C++语法(25)--- 异常与智能指针_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/131537799?spm=1001.2014.3001.5501
目录
1.特殊类设计
1.设计一个类,不能被拷贝
C++98
C++11
2.设计一个类,只能在堆上创建对象
1.封掉所有拷贝
C++98
C++11
2.封掉析构函数
3.设计一个类,只能在栈上创建对象
4.设计一个类,不能被继承
1.C++98
2.C++11 final
2.单例模式
1.设计模式
2.单例模式
1.饿汉模式
2.懒汉模式
3.单例对象释放问题
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.特别注意,其实拷贝构造函数也需要被处理一下,因为如果在外部先构造堆上的,随后通过解指针调用给拷贝构造,那么其实也是不算合格的。
class HeapOnly { public:static HeapOnly* CreateObject(){return new HeapOnly;} private:HeapOnly() {}HeapOnly(const HeapOnly&) {} }int main() {HeapOnly* php = HeapOnly::CreateObject(); }
C++11
class 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
class A final {// .... };
2.单例模式
1.设计模式
设计模式是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
一些设计理念:迭代器模式,配接器模式,单例模式,工厂模式,观察者模式
2.单例模式
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
使用样例:内存池的申请1.饿汉模式
特点:
一开始(在main函数之前)就创造对象,就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象
缺点:
1.由于初始化在main函数之前,这样的类数据过多,会使得启动慢;
2.多个单例类有初始化依赖关系,饿汉模式无法控制类的初始化先后关系
class 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.如果类之间存在依赖关系,也可以使用懒汉模式(延迟加载)。
template<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后得到解决。
static _InfoSingleton& GetInstance() {static _InfoSingleton sinst;return sinst; }
注意:
1.懒汉模式需要注意线程安全问题,所以我们在类中需要有一个唯一的锁,确保判断时是串行访问的。
2.每次都先加锁再进行判断是否为空,其实是非常低效,所以我们需要双判断,第一次判断是为了抛去已经创建过的节省加锁的时间,第二次判断是为了创建对象使用的,而锁夹在中间确保第二次的判断是串行的。
3.饿汉模式不需要注意线程安全问题,因为饿汉在main调用之前就已经存在了,没有所谓的线程可以创建其他的对象。
3.单例对象释放问题
1.一般而言单例类不需要释放内存,因为单例出现的环境就是全局的,它的目的就是陪到进程执行到最后,那么其实不释放,进程结束后,操作系统也会将这一部分的资源回收。
2.特别的,如果我们需要在最后单例有一定要求,我们可以手写出析构,比如进程结束需要保存一些数据到文件中,那么我们析构可以手写要求。