C++ 中比较主要的模式设计:适配器、迭代器、单例(工厂模式、观察者模式…)本篇重点为单例模式。
拷贝只会发生在两个场景中:拷贝构造函数 以及 赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
C++98:
将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。
原因:
1. 设置成私有:如果只声明没有设置成 private,用户自己如果在类外定义了,
就不能禁止拷贝了
2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,
不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
C++11:
C++11 扩展 delete 的用法,delete 除了释放 new 申请的资源外,如果在默认成员函数后跟上= delete
,表示让编译器删除掉该默认成员函数。
思路1:析构函数 设成私有,普通对象就不给创建了,同时建立一个公共函数包装析构函数,需要析构的时候显示调用该函数。
class HeapOnly1
{
public:
void Destroy()
{
delete this;
}
private:
~HeapOnly1()
{
cout << "~HeapOnly()" << endl;
}
int _x;
};
int main1()
{
//HeapOnly1 ho1; // err..私有的析构调不动了,所以不给这样写
//static HeapOnly1 ho2; // err..
HeapOnly1* pho3 = new HeapOnly1;
pho3->Destroy();
return 0;
}
思路2:构造函数 设成私有,并提供一个在堆上创建对象的接口,还需要 delete 掉拷贝和赋值。(不推荐)
class HeapOnly
{
public:
static HeapOnly* CreateObj(int x = 0) // 没有static也不行,创建对象需要这个函数,调这个函数首先又要有个对象,设成静态就好了
{
HeapOnly* p = new HeapOnly(x);
return p;
}
private:
HeapOnly(int x = 0)
:_x(x)
{}
HeapOnly(const HeapOnly& hp) = delete; // 排除拷贝的风险(因为拷贝构造出来的对象还是在栈上)
HeapOnly& operator=(const HeapOnly& hp) = delete; // 赋值也顺便封蛤喽
int _x;
};
int main2()
{
//HeapOnly ho1; // err..
//static HeapOnly ho2; // err..
//HeapOnly* pho3 = new HeapOnly; // err..
HeapOnly* p1 = HeapOnly::CreateObj(1);
//HeapOnly p2(*p1); // err..
return 0;
}
思路:构造函数 设为私有,并提供一个在栈上创建对象的接口
class StackOnly
{
public:
static StackOnly CreateObj(int x = 0)
{
return StackOnly(x);
}
StackOnly(StackOnly&& st)
:_x(st._x)
{}
private:
StackOnly(int x = 0)
:_x(x)
{}
StackOnly(const StackOnly& st) = delete; // 拷贝封了,正常不能传值返回了,我们显式写一个移动构造,但其实这样也不能防止static对象去调move()
int _x;
};
int main3()
{
/*StackOnly st1;
static StackOnly st2;
StackOnly* st3 = new StackOnly;*/ // err..
StackOnly st1 = StackOnly::CreateObj(1);
//static StackOnly st2 = st1; // err..
//static StackOnly st2 = move(st1); // 防止不了,貌似没有很好的方法
return 0;
}
C++98:
构造函数 设成私有,派生类钓不到基类的构造函数,无法继承。
C++11:
final
关键字声明,声明的类不能被继承。
保证一些数据(一个进程中)全局只有唯一一份,并且方便访问
利用 静态成员变量,一开始(在 main 函数之前) 就创建对象
class Singleton
{
public:
static Singleton* GetInstance() // 提供一个公有的成员函数
{
return _ins;
}
void Add(const string& str)
{
_mtx.lock();
_v.push_back(str);
_mtx.unlock();
}
void Print()
{
_mtx.lock();
for (auto& e : _v)
{
cout << e << endl;
}
cout << endl;
_mtx.unlock();
}
private:
// 限制类外面随意创建对象:构造函数私有化
Singleton()
{}
// 防拷贝
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
private:
mutex _mtx;
vector<string> _v;
static Singleton* _ins; // 静态的成员,必须在类外初始化
};
Singleton* Singleton::_ins = new Singleton; // 可以调用私有是因为这是类中声明类外定义的问题,本质还是一个类
// new对象不会有线程安全问题,因为是在main函数以前不会出现线程安全问题
int main4()
{
/*Singleton s1;
static Singleton s1;*/
Singleton::GetInstance()->Add("hello Kevin");
Singleton::GetInstance()->Add("hello Stella");
Singleton::GetInstance()->Add("hello Kim");
Singleton::GetInstance()->Print();
return 0;
}
类在 第一次访问 实例对象 时创建
class Singleton
{
public:
static Singleton* GetInstance() // 每次获取的时候都要加锁解锁
{
// 双检查加锁
if (_ins == nullptr) // 这是为了提高效率,不需要每次获取单例都加锁解锁
{
_imtx.lock();
if (_ins == nullptr) // 保证线程安全和只new一次,这里必须还要检查一次
{
_ins = new Singleton;
}
_imtx.unlock();
}
return _ins;
}
// 一般全局都要使用单例对象,所以单例对象一般不需要显示释放
// 如果有些特殊场景,想显示释放一下,如下:
static void DelInstance()
{
_imtx.lock();
if (_ins)
{
delete _ins;
_ins = nullptr;
}
_imtx.unlock();
}
// 有如果忘记调用 DelInstance 了?
// 定义一个内部类
// 可以保证单例对象回收:
class GC
{
public:
~GC()
{
DelInstance();
}
};
static GC _gc;
void Add(const string& str)
{
_vmtx.lock();
_v.push_back(str);
_vmtx.unlock();
}
void Print()
{
_vmtx.lock();
for (auto& e : _v)
{
cout << e << endl;
}
cout << endl;
_vmtx.unlock();
}
~Singleton()
{
// 持久化
// 比如要求程序结束时,将数据写到文件,单例对象析构时持久化就比较好
}
private:
// 限制类外面随意创建对象
Singleton()
{}
// 防拷贝
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
private:
mutex _vmtx;
vector<string> _v;
static Singleton* _ins;
static mutex _imtx;
};
Singleton* Singleton::_ins = nullptr;
mutex Singleton::_imtx; // 锁的初始化不需要显示的给值
Singleton::GC Singleton::_gc;
int main5()
{
srand(time(0));
int n = 30;
thread t1([n]() {
for (size_t i = 0; i < n; ++i)
{
Singleton::GetInstance()->Add("t1线程:" + to_string(rand()));
}
});
thread t2([n]() {
for (size_t i = 0; i < n; ++i)
{
Singleton::GetInstance()->Add("t2线程:" + to_string(rand()));
}
});
t1.join();
t2.join();
Singleton::GetInstance()->Print();
Singleton::GetInstance();
//Singleton s(*Singleton::GetInstance()); // 此时拷贝不动只是因为有锁,没锁还是可以拷贝的,怎么防止咧?delete拷贝和赋值
return 0;
}
// 这样写也是可以的,C++11之后才可以,C++11出来之前没法保证这里的线程安全
class Singleton
{
public:
static Singleton* GetInstance()
{
// C++11之前,这里不能保证初始化静态对象的线程安全问题
// C++11之后,这里可以保证初始化静态对象的线程安全问题
static Singleton inst;
return &inst;
}
void Add(const string& str)
{
_vmtx.lock();
_v.push_back(str);
_vmtx.unlock();
}
void Print()
{
_vmtx.lock();
for (auto& e : _v)
{
cout << e << endl;
}
cout << endl;
_vmtx.unlock();
}
~Singleton()
{
// 持久化
// 比如要求程序结束时,将数据写到文件,单例对象析构时持久化就比较好
}
private:
// 限制类外面随意创建对象
Singleton()
{
cout << "Singleton()" << endl;
}
// 防拷贝
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
private:
mutex _vmtx;
vector<string> _v;
};
int main()
{
Singleton::GetInstance();
Singleton::GetInstance();
return 0;
}
懒汉完美的解决了上面饿汉的问题,只是相对复杂一点点
【4】
【创建一个类,不能被继承】
C++98:构造函数 设成私有,派生类钓不到基类的构造函数,无法继承。
C++11:final 关键字声明,声明的类不能被继承。
C++中比较主要的模式设计:
适配器
迭代器
单例
扩展学习:工厂模式、观察者模式、
【5】
【设计一个类,只能创建一个对象(单例模式)】
保证一些数据(一个进程中)全局只有唯一一份,并且方便访问
1. 把这些数据放进一个类里,把这个类设计成单例类
2. 构造和拷贝构造都封死,提供一个static公有获取单例对象的函数
3. 如何创造单例对象,饿汉 or 懒汉