特殊类设计(单例模式)
文章目录
今天忙活了一天写了一个线程池,写完我才发现单例模式的重要性🤗,做如下学习记录
设计一个类——不能被拷贝
- 解决思路一:在C++11之前的C++98,我们可以把拷贝构造函数和赋值重载函数定义成
private
,这样外部就无法拷贝了
class A
{
private:A(const A& x) ;A& operator=(const A& x);
};
- 在C++11之后引入了关键字
delete
我们可以直接将拷贝构造和赋值重载函数删除
class A
{
public:A(const A& x) = delete;A& operator=(const A& x) = delete;
};
设计一个类——只能在堆上开辟空间
首先想明白一个问题:构造函数是否需要删除?
答案是明显的:不能,因为在堆上开辟空间new
需要调用构造函数,如果删除了就无法开辟空间了,我们这里要做的是将构造函数隐藏,并封装一个函数使其只能调用new
来构造对象
class A
{
public:static A* heap_only(){return new A;}
private:A(){//....}
};
设计一个类——只能在栈上开辟空间
这个思路就是将operator new
或者将 operator delete
给删除或者放到private
作用域中
class A
{
public:void* operator new (size_t a) = delete;void operator delete(void *) = delete;
};
设计一个类——不能被继承
- 方法一:将基类的构造函数放到
private
作用域之中,这是因为子类的构造函数一定会调用基类的构造函数,如果基类的构造函数不可见,那么它就无法被继承 - 方法二:使用关键字
final
去修饰
class A final
{//....
};
设计一个类——只能创造一个对象(单例模式)
设计模式:
设计模式我认为是工程文件的一种技巧,是一种被反复使用、多数人知晓、经过分类的、代码经验的总结
单例模式:
一个类只能创建一个对象,这就是单例模式。单例模式的类保证系统中该类只有一个实例,并可以被所有模块访问这一个实例。
饿汉模式
我想先看饿汉模式的设计:👇
class A
{
public:static A& gey_singleton(){return singleton;}A(const A&) = delete;A& operator=(const A&) = delete;
private:A(){//....}static A singleton;
};
A A::singleton;
有如下几个要点:
- 首先这个单例对象
singleton
必须是static
对象,这样保证了一个类只有一个对象 - 其次构造函数必须设置成为私有,这样类外无法构造出对象,但是类的成员
singleton
能构造出对象 - 最后我们建立一个接口函数
singleton
使得类外部能够拿到这个单例对象,但是这个接口函数必须设计成静态成员函数,如果不是静态的就会造成——因为你想调用这个成员函数就必须有这个类的对象,如果你像要得到这个类的对象就必须调用这个类的成员函数😅。所以我们这里定义成静态成员变量直接使用类域就可以调用 - 拷贝构造和负值重载函数要删除
懒汉模式
class A
{
public:static A* get_singleton(){if (singleton == nullptr)singleton = new A;return singleton;}//防止拷贝A(const A&) = delete;A& operator=(const A&) = delete;
private:A(){//..}static A* singleton;
};
A* A::singleton = nullptr;
懒汉模式的设计思路与饿汉模式设计思路大体上是一致的,唯一区别就是懒汉模式的单例对象singleton
是一个指针,而饿汉模式的单例对象singleton
是一个对象。
区别
首先补充一点知识:静态和全局数据在编译链接之后的可执行文件之中就是存在的,而我们堆栈则是在运行的时候才生成的。可执行文件运行的时候要经历一步加载,由加载器来完成该步骤——也就是创立进程的结构、建立虚拟内存到物理内存的映射。如果一个可执行文件越大,加载的速度也就越慢。
- 所以饿汉模式在进程加载完之后就已经实例化出了单例对象,
- 如果单例对象空间大,会导致程序启动缓慢
- 如果你的进程在后半段才会使用单例对象,但是进程刚开始就初始化好了单例对象,实际上是一种资源的浪费
- 但是懒汉模式也有优点那就是简单
线程安全问题
上面的代码实际上还是有一些不足的:
以饿汉模式为例,单例模式算是一个公共资源,所有模块都能访问就会有线程安全问题,所以我们要设计一个线程安全的单例模式
class A
{
public:static A* get_singleton(){//双判断可以避免单例已经被创建,但是任然有多个线程申请锁、判断、释放锁的无用开销if (singleton == nullptr){m.lock();if (singleton == nullptr)singleton = new A;m.unlock();}return singleton;}A(const A&) = delete;A& operator=(const A&) = delete;
private:A(){//..}static A* singleton;static mutex m;
};
A* A::singleton = nullptr;