编写一个程序,设计加油站类,此类包含4个私有数据:工作员名,当天所用无铅汽油量,有铅汽油量以及总输入(无铅汽油的价格是17元/公升,有铅汽油价格是16/公升)。试输入某天3个工作人员所加的汽油量,输出加油站当天的总收入
#include <iostream>
#include <string>
using namespace std;
class GasStation {
private:
string workerName;
double unleadedGas;
double leadedGas;
double totalInput;
public:
GasStation(string name, double uGas, double lGas) {
workerName = name;
unleadedGas = uGas;
leadedGas = lGas;
totalInput = unleadedGas * 17 + leadedGas * 16;
}
double getTotalInput() {
return totalInput;
}
};
int main() {
double uGas1, lGas1, uGas2, lGas2, uGas3, lGas3;
cout << "Please enter the amount of unleaded gasoline and leaded gasoline for worker 1: ";
cin >> uGas1 >> lGas1;
cout << "Please enter the amount of unleaded gasoline and leaded gasoline for worker 2: ";
cin >> uGas2 >> lGas2;
cout << "Please enter the amount of unleaded gasoline and leaded gasoline for worker 3: ";
cin >> uGas3 >> lGas3;
GasStation worker1("Worker 1", uGas1, lGas1);
GasStation worker2("Worker 2", uGas2, lGas2);
GasStation worker3("Worker 3", uGas3, lGas3);
double totalInput = worker1.getTotalInput() + worker2.getTotalInput() + worker3.getTotalInput();
cout << "The total income of the gas station for the day is: " << totalInput << " yuan" << endl;
return 0;
}
定义了一个名为GasStation的加油站类,该类包含了4个私有数据成员,分别为工作员名(workerName)、当天所用无铅汽油量(unleadedGas)、有铅汽油量(leadedGas)以及总输入(totalInput)。类的构造函数初始化这些私有数据成员,并计算出总输入。
主函数中,用户需要输入3个工作人员所加的汽油量。然后分别创建3个GasStation对象来表示3个工作人员的加油量。程序计算出加油站当天的总收入,输出到控制台中
示例代码
#include <iostream>
using namespace std;
class GasStation {
private:
string workerName;
double unleadedGas;
double leadedGas;
double totalInput;
public:
GasStation(string name, double unleaded, double leaded) {
workerName = name;
unleadedGas = unleaded;
leadedGas = leaded;
totalInput = unleaded * 17 + leaded * 16;
}
double getTotalInput() {
return totalInput;
}
};
int main() {
GasStation worker1("John", 30, 20);
GasStation worker2("Mary", 40, 25);
GasStation worker3("Bob", 20, 10);
double totalInput = worker1.getTotalInput() + worker2.getTotalInput() + worker3.getTotalInput();
cout << "Total income of the gas station today is $" << totalInput << endl;
return 0;
}
不知道你这个问题是否已经解决, 如果还没有解决的话:**
#pragma
#include <stdio.h>
#include <stdlib.h>
#include <mutex>
class CSingletion4
{
public:
CSingletion4* GetInstance()
{
if (NULL == m_pStaticSingletion)
{
std::lock_guard<std::mutex> lock(m_mutex);
if (NULL == m_pStaticSingletion)
{
atexit(DeleteInstance);
m_pStaticSingletion = new CSingletion4();
}
}
return m_pStaticSingletion;
}
//todo 实现功能
void work();
private:
static void DeleteInstance()
{
if (NULL != m_pStaticSingletion)
{
delete m_pStaticSingletion;
m_pStaticSingletion = NULL;
}
}
CSingletion4();
CSingletion4(const CSingletion4&);
CSingletion4& operator = (const CSingletion4&);
static CSingletion4* m_pStaticSingletion;
static std::mutex m_mutex;
};
CSingletion4* CSingletion4::m_pStaticSingletion = nullptr;//类静态成员需要外部初始化
std::mutex CSingletion4::m_mutex;
所以我们可以将锁放在if(NULL == **)的语句的下面,如此,相比较版本三的代码,不管m_pStaticSingletion 是否为空,进来就加一次锁,版本四只会在m_pStaticSingletion 为空时加锁,会好很多, 那我们来讨论一种情况,当m_pStaticSingletion为空时,两个线程同时调用GetInstance函数,第一个线程的函数调用,已经进入到锁中了,
这里说明两个知识点
CPU指令重排,简单明了。
- m_pStaticSingletion = new CSingletion4();这条语句翻译为汇编指令或者说CPU指令主要分为三个过程,1.分配内存,2.调用构造函数, 3将内存首地址赋给m_pStaticSingletion 。
- cpu指令重排,简单地说,就是cpu中是通过流水线的方式来执行指令的,在不影响程序上下文的情况下, Cpu为了提高效率会对指令进行重排序,以适合cpu的顺序运行。但是指令重排会遵守As-if-serial的规则,就是所有的动作(Action)都可以为了优化而被重排序,但是必须保证它们重排序后的结果和程序代码本身的应有结果是一致的。所以这种情况在单线程中不会出现什么问题。而对于多线程,这个规则就失效了,所以可能会导致结果出现问题。这里不多赘述,推荐一个博客
说白了还是优化出的问题,在java中有volatile可以禁止优化指令重排,但是C++volatile关键字不是这么用,言归正传,当第一个线程函数在执行到m_pStaticSingletion = new CSingletion4();这句话的时候,cpu进行了指令重排,就是cpu本来的执行顺序是1.分配内存,2.调用构造函数, 3将内存首地址赋给m_pStaticSingletion ,但是实际上步骤1执行完了,cpu中的一个流水线在执行步骤2,可能这个函数构造比较慢,当前cpu流水线发生了堵塞,那其他的cpu指令流水线不会等待,继续从指令缓冲区中取下一个指令步骤三,这个时候就是m_pStaticSingletion 指向的内存还未初始化,那么刚好,第二个线程执行到if (NULL == m_pStaticSingletion)语句的时候判断m_pStaticSingletion 不为空,直接将m_pStaticSingletion 返回出去,那么该线程在线程一构造函数为执行完成之前,调用该指针势必会导致程序异常,甚至崩溃。
综上所述,咱们的不可重入版本4的代码还是存在瑕疵, 所以引出了版本五,
#pragma
#include <stdio.h>
#include <stdlib.h>
#include <mutex>
#include <atomic>
class CSingletion5
{
public:
static CSingletion5* GetInstance()
{
CSingletion5* tmp = m_pStaticSingletion.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);//获取内存屏障
if (NULL == tmp)
{
std::lock_guard<std::mutex> lock(m_mutex);
tmp = m_pStaticSingletion.load(std::memory_order_relaxed);
if (NULL == tmp)
{
tmp = new CSingletion5();
std::atomic_thread_fence(std::memory_order_release);//释放内存屏障
m_pStaticSingletion.store(tmp, std::memory_order_relaxed);
atexit(DeleteInstance);
}
}
return m_pStaticSingletion;
}
//todo 实现功能
void work();
private:
static void DeleteInstance()
{
if (NULL != m_pStaticSingletion)
{
delete m_pStaticSingletion;
m_pStaticSingletion = NULL;
}
}
CSingletion5();
CSingletion5(const CSingletion5&);
CSingletion5& operator = (const CSingletion5&);
static std::atomic<CSingletion5*> m_pStaticSingletion;
static std::mutex m_mutex;
};
std::atomic<CSingletion5*> CSingletion5::m_pStaticSingletion = nullptr;//类静态成员需要外部初始化
std::mutex CSingletion5::m_mutex;
使用C++11提供的原子控制类atomic,加上内存屏障,防止cpu指令reorder,这个时候大家可能会觉得这个代码过于复杂,有没有更简单一点的方法呢,有的看代码六
#pragma
//c++ 11 magic static 特性 当变量在初始化的时候,并且同时进入声明语句是,并发线程会阻塞等待初始化结束。
class CSingletion6
{
public:
static CSingletion6& GetInstance()
{
//局部静态变量,在数据段,程序结束时系统会自动回收
static CSingletion6 Singletion;
return Singletion;
}
//todo 实现功能
void work();
private:
CSingletion6();
CSingletion6(const CSingletion6&);
CSingletion6& operator = (const CSingletion6&);
};
这个代码看起来是非常的清爽了,非常的舒服,但是这个代码还是存在一些问题,因为构造函数是私有的·所以会造成无法继承以及拓展性太差的问题,为了解决这个问题我们提出了代码7,
#pragma once
//c++ 11 magic static 特性 当变量在初始化的时候,并且同时进入声明语句是,并发线程会阻塞等待初始化结束。
template<typename T>
class CSingletion7
{
public:
static T& GetInstance()
{
//局部静态变量,在数据段,程序结束时系统会自动回收
static T Singletion;
return Singletion;
}
virtual ~CSingletion7() {}
//todo 实现功能
void work();
protected:
CSingletion7();
CSingletion7(const CSingletion7&);
CSingletion7& operator = (const CSingletion7&);
};
class CSingletion8 : public CSingletion7<CSingletion8>
{
friend class CSingletion7<CSingletion8>;//friends 让CSingletion7<CSingletion8>访问到CSingletion8的构造函数
//to do something else
void Externwork();
private:
CSingletion8();
CSingletion8(const CSingletion8&);
CSingletion8& operator = (const CSingletion8&);
};
大家仔细看这个模板类的代码,我们将基类的构造函数可见性修改为protected,目的是为了让派生类可以访问到基类的构造函数,而将子类定义为派生类的友元类,是为了在基类中能够访问派生类的构造函数,这样我们在基类中定义了一个派生类对象,且该对象是唯一的,该对象既可以访问派生类的所有方法也可以访问子类的所有方法,至此大功告成。