目录
[2.C++11 final](#2.C++11 final)
1.特殊类设计
1.设计一个类,不能被拷贝
C++98
将拷贝函数只声明不实现,并且放到私有处。(只不过,在类型内部调用另外的函数进行拷贝就没有办法防止了)
class CopyBan { // ... private: CopyBan(const CopyBan&); CopyBan& operator=(const CopyBan&); //... };
C++11
在函数申明后面加入 =delete
class CopyBan { // ... CopyBan(const CopyBan&)=delete; CopyBan& operator=(const CopyBan&)=delete; //... };
2.设计一个类,只能在堆上创建对象
1.封掉所有拷贝
C++98
1.先将构造函数封死,这样就无法在栈上创造对象了。但是我们需要提供一个函数调用,使得其在堆上创造对象。
2.创造堆上的函数不能直接修饰,这样会出现逻辑谬论(即想要创造对象,需要调用函数;想要调用函数,需要先创造对象)。所以这里的解决方式是设置静态函数,不需要this指针,所以不需要生成所谓对象才能调用,可以直接调用。
3.特别注意,其实拷贝构造函数也需要被处理一下,因为如果在外部先构造堆上的,随后通过解指针调用给拷贝构造,那么其实也是不算合格的。
cppclass HeapOnly { public: static HeapOnly* CreateObject() { return new HeapOnly; } private: HeapOnly() {} HeapOnly(const HeapOnly&) {} } int main() { HeapOnly* php = HeapOnly::CreateObject(); }
C++11
cppclass HeapOnly { public: static HeapOnly* CreateObject() { return new HeapOnly; } HeapOnly(const HeapOnly&) = delete; HeapOnly(const HeapOnly&) = delete; };
2.封掉析构函数
1.将析构函数封掉也能达到相同的效果,因为构造了就需要被析构,类型没有检查到可以析构就会报错。
2.此时堆上的类需要被释放掉,也需要重新构造一个destory函数,其类型是类型的指针,不可以是对象本身,因为这样就是手动调用析构函数了。只要调用destory就能释放堆上的空间。
3.设计一个类,只能在栈上创建对象
这样设计使得不能创建在堆和静态上。
class StackOnly { public: static StackOnly CreateObj() { return StackOnly(); } private: StackOnly() :_a(0) {} private: int _a; };
这样的设计使得不能在堆上创建但是,能产生静态的对象。
// 禁掉operator new可以把下面用new 调用拷贝构造申请对象给禁掉 void* operator new(size_t size) = delete; void operator delete(void* p) = delete;
4.设计一个类,不能被继承
1.C++98
构造函数私有化,子类本来要调用父类的构造函数,没有则不能被继承。
class NonInherit { public: static NonInherit GetInstance() { return NonInherit(); } private: NonInherit() {} };
2.C++11 final
cppclass A final { // .... };
2.单例模式
1.设计模式
设计模式是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
一些设计理念:迭代器模式,配接器模式,单例模式,工厂模式,观察者模式
2.单例模式
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
使用样例:内存池的申请
1.饿汉模式
特点:
一开始(在main函数之前)就创造对象,就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象
缺点:
1.由于初始化在main函数之前,这样的类数据过多,会使得启动慢;
2.多个单例类有初始化依赖关系,饿汉模式无法控制类的初始化先后关系
cppclass InfoSingleton { public: static InfoSingleton& GetInstance() { return _sins; } void Insert(string name, int money) { _info[name] = money; } void Print() { for (auto kv : _info) { cout << kv.first << " " << kv.second << endl; } } private: InfoSingleton() {} InfoSingleton(const InfoSingleton& info) = delete; InfoSingleton& operator=(const InfoSingleton& info) = delete; map<string, int> _info; private: static InfoSingleton _sins; }; InfoSingleton InfoSingleton::_sins; int main() { InfoSingleton::GetInstance().Insert("张三", 1000); InfoSingleton& info = InfoSingleton::GetInstance(); info.Insert("李四", 100); //InfoSingleton copy = InfoSingleton::GetInstance(); //拷贝构造 //copy.Insert("***", 10000); return 0; }
2.懒汉模式
1.如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
2.如果类之间存在依赖关系,也可以使用懒汉模式(延迟加载)。
cpptemplate<class Lock> class LockGuard { public: LockGuard(Lock& lk) :_lk(lk) { _lk.lock(); } ~LockGuard() { _lk.unlock(); } private: Lock& _lk; }; class _InfoSingleton { public: //线程安全问题,多线程一起调用创建对象 static _InfoSingleton& GetInstance() { //双检查增加效率 if (_psins == nullptr) { LockGuard<mutex> lock(*_smtx); if (_psins == nullptr) { _psins = new _InfoSingleton; } } return *_psins; } void Insert(string name, int money) { _info[name] = money; } void Print() { for (auto kv : _info) { cout << kv.first << " " << kv.second << endl; } } private: _InfoSingleton() {} _InfoSingleton(const _InfoSingleton& info) = delete; _InfoSingleton& operator=(const _InfoSingleton& info) = delete; map<string, int> _info; private: static _InfoSingleton* _psins; static mutex* _smtx; }; static _InfoSingleton* _psins = nullptr; static mutex* _smtx;
1.这样写是懒汉模式,只创建一次,并且在main函数调用之后创建。
2.该代码有线程安全问题,在C++11后得到解决。
cppstatic _InfoSingleton& GetInstance() { static _InfoSingleton sinst; return sinst; }
注意:
1.懒汉模式需要注意线程安全问题,所以我们在类中需要有一个唯一的锁,确保判断时是串行访问的。
2.每次都先加锁再进行判断是否为空,其实是非常低效,所以我们需要双判断,第一次判断是为了抛去已经创建过的节省加锁的时间,第二次判断是为了创建对象使用的,而锁夹在中间确保第二次的判断是串行的。
3.饿汉模式不需要注意线程安全问题,因为饿汉在main调用之前就已经存在了,没有所谓的线程可以创建其他的对象。
3.单例对象释放问题
1.一般而言单例类不需要释放内存,因为单例出现的环境就是全局的,它的目的就是陪到进程执行到最后,那么其实不释放,进程结束后,操作系统也会将这一部分的资源回收。
2.特别的,如果我们需要在最后单例有一定要求,我们可以手写出析构,比如进程结束需要保存一些数据到文件中,那么我们析构可以手写要求。