目录
一个不能被拷贝的类
一个只能创建在堆上的类
一个只能创建在栈上的类
一个不能被继承的类
一个只能创建一个对象的类(单例模式)
饿汉模式
懒汉模式
一个不能被拷贝的类
通常拷贝只会发生在拷贝构造和赋值运算符重载这两个场景下,所以想要一个类禁止拷贝就不要让他调用这两个函数。
C++98中要这样做就需要只声明不实现,并将其设为私有。
class A { private:A(const A& a);A& operator=(const A& a); }
- 如果只声明没有设置成private,用户自己如果在类外定义了,就不能禁止拷贝了。
C++11中只需要使用delete关键字就可以,表示删除这个函数。
class A {A(const A& a)=delete;A& operator=(const A& a)=delete; }
一个只能创建在堆上的类
这种类在之前C++11的篇章也提到过,那是一种处理析构函数的方法,下面介绍一种处理构造函数的方法。
- 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
- 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建。
如果不使用static,那么这个类没有实例化,那就不能调用这个创建对象的函数。
class A { public:static A* CreateObj() // 控制这个类只能在堆上创建{return new A;} private:A() {}// C++98是把拷贝构造也私有// A(const A& a) {}// C++11是使用deleteA(const A& a) = delete; };int main() {A* pa;pa->CreateObj();delete pa;return 0; }
一个只能创建在栈上的类
还是可以将构造函数私有,使用静态函数创建在栈上的对象并返回。
class A { public:static A CreateObj(){return A();}// 这里不可以禁用拷贝和赋值运算符,如果禁掉那么A pa = A::CreateObj();这种方式就不能实现了// 禁掉operator new可以把下面用new 调用拷贝构造申请对象给禁掉,但是这样禁用有的方式还是可以创建在静态区,算是一个缺陷// A obj = A::CreateObj();// A* ptr3 = new A(obj);void* operator new(size_t size) = delete;void operator delete(void* p) = delete; private:A() {} };int main() {A pa = A::CreateObj();return 0; }
一个不能被继承的类
C++98中是把构造函数私有,子类调用不到父类的构造函数,就没有办法继承。
class A { public:static A GetInstance(){return A();} private:A() {} };
C++11使用final关键字表示类不能被继承
class A final {// }
一个只能创建一个对象的类(单例模式)
单例模式是一种设计模式,设计模式是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。它的目的就是为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石。
该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
单例模式有两种实现模式:
饿汉模式
在main函数之前就创建出对象,就像饿汉一样,一开始就“全吃了”。
class Singleton { public:static Singleton* GetInstance(){return _pinst;}void* Alloc(size_t n){void* ptr = nullptr;// ...return ptr;}void Dealloc(){// ...} private:// 构造函数私有Singleton(){}char* _ptr = nullptr;// static Singleton _inst; // 声明一个在静态区的Singleton对象static Singleton* _pinst; // 这是该类的对象,可以访问类中的函数 };// 定义 // Singleton Singleton::_inst; Singleton* Singleton::_pinst = new Singleton;int main() {void* ptr = Singleton::GetInstance()->Alloc(2);return 0; }
饿汉模式是一个静态成员在main函数之前就创建出来的,所以不存在多线程,也就不会有线程安全的问题,不允许随便创建、不允许拷贝。
饿汉模式优点就是简单。缺点一是没有办法确定几个单例类的实例顺序;二是假如一个类的启动之前在构造函数进行了很多步骤,可能会导致进程启动慢。
懒汉模式
如果单例对象构造十分耗时或者占用很多资源,但是有的对象用不到,饿汉模式也要在一开始初始化,这样就会导致启动非常慢,这种就要使用懒汉模式,懒汉模式对应饿汉模式的缺点,就像懒汉一样,什么时候用什么时候实例化,什么时候有什么时候吃。它就可以控制实例顺序。懒汉模式是使用延迟加载的思想。但是它很复杂。
class Singleton { public:static Singleton* GetInstance(){if (_pinst == nullptr){_pinst = new Singleton;}return _pinst;}void* Alloc(size_t n){void* ptr = nullptr;// ...return ptr;}void Dealloc(){// ...} private:// 构造函数私有Singleton(){}Singleton(Singleton const&) = delete;Singleton& operator=(Singleton const&) = delete;char* _ptr = nullptr;// 声明static Singleton* _pinst; };// 定义 Singleton* Singleton::_pinst = nullptr;
懒汉模式和饿汉模式的特点差不多,无非就是懒汉模式需要的时候才创建,那这就会带来线程安全的问题,多个线程同时访问,第一次多个线程要竞争的创建类,创建好之后就不要再创建了。可以拿线程池作参考。
class Singleton { public:static Singleton* GetInstance(){// 只有第一次为空的时候才创建,如果不为空直接返回,这样就只new了一次// 双检查加锁if (_pinst == nullptr) // 已经创建了就直接返回,没有就加锁创建{unique_lock<mutex> lock(_mutex); // RAII的加锁方式if (_pinst == nullptr)_pinst = new Singleton;}return _pinst;// ...// 声明static Singleton* _pinst;static mutex _mutex; };// 定义 Singleton* Singleton::_pinst = nullptr; mutex Singleton::_mutex;
特殊的懒汉模式:
class Singleton { public:static Singleton* GetInstance(){// 局部静态对象是在调用的时候初始化static Singleton _s;return &_s;} };
上面的这种方法在C++11之前是不能保证线程安全的,因为在这之前局部静态对象的构造函数调用初始化并不能保证线程安全,不能保证创建是否是原子的,所以在C++11后修复了这个问题,这种写法只能在支持C++11的编译器上。
单例对象的释放问题:
- 一般情况下,单例对象不需要释放,一般整个程序运行期间都会用它。
- 单例对象在进程正常结束后也会释放。有些特殊场景也需要释放,比如单例对象析构时,要进行持久化操作,如往磁盘上写,这时就可以写一个内部类,等到程序结束,判断对象是否还在,在就释放。