北京亦庄做网站公司,夺宝网站建设,运城网站制作路90,国家城乡和住房建设部网站1.前言 在实际场景中,总会遇见一些特殊情况,比如设计一个类,只能在堆上开辟空间, 或者是设计一个类只能实例化一个对象。那么我们应该如何编写代码呢#xff1f;本篇将会详细的介绍 本章重点#xff1a;
本篇文章着重讲解如何设计一些特殊 的类,包括不能被拷贝,只能在栈/堆上…1.前言 在实际场景中,总会遇见一些特殊情况,比如设计一个类,只能在堆上开辟空间, 或者是设计一个类只能实例化一个对象。那么我们应该如何编写代码呢本篇将会详细的介绍 本章重点
本篇文章着重讲解如何设计一些特殊 的类,包括不能被拷贝,只能在栈/堆上 创建对象以及此类只能实例化一个对象, 这也是我们听说过的单例模式,单例模式又 包含饿汉和懒汉模式,文章都是干货 请同学们耐心学习!
2.设计一个不能被拷贝/继承的类
1.设计一个类不能被拷贝 在设计这个类之前需要明确的是拷贝只会放生在两个场景中拷贝构造函数以及赋值运算符重载因此想要让一个类禁止拷贝 只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。 在c98中一般操作就是在私有中把这两个函数只声明不定义。
class CopyBan
{private:CopyBan(const CopyBan);CopyBan operator(const CopyBan);
};
原因 1. 设置成私有如果只声明没有设置成private用户自己如果在类外定义了就可以不 能禁止拷贝了 2. 只声明不定义不定义是因为该函数根本不会调用定义了其实也没有什么意义不写 反而还简单而且如果定义了就不会防止成员函数内部拷贝了。 C11扩展 delete 的用法 delete 除了释放 new申请的资源外如果在默认成员函数后跟上 delete表示让编译器删除掉该默认成员函数。 class CopyBan
{CopyBan(const CopyBan)delete;CopyBan operator(const CopyBan)delete;
};2.设计一个不能被继承的类
c11中可以直接使用关键字final
class A final
{// ....
};c98基类的构造函数私有化这样子类在创建对象时属于基类的那一部分会调用基类的构造函数初始化那么由于基类的构造函数私有化了因此无法初始化直接报错。
class NonInherit
{
private:NonInherit(){}
};3.设计只能在堆上创建对象的类
只能在堆上创建对象那么就表明所有创建的对象经过的构造函数都要用new来构造这样才能够满足要求。
除此之外还要禁止拷贝构造和赋值函数因为这样可以防止别人调用拷贝或者赋值重载函数来生成对象
class HeapOnly
{
public: static HeapOnly* CreateObject() { return new HeapOnly; }
private: HeapOnly() {}// C98// 1.只声明,不实现。因为实现可能会很麻烦而你本身不需要// 2.声明成私有HeapOnly(const HeapOnly)// or// C11 HeapOnly(const HeapOnly) delete;
};
解释为什么这里使用的是static: 将构造函数设置为私有后,不管是在堆上还是栈上都不能创建对象,但是我们可以在共有域写一个函数显示去调用构造函数,注意,这里的共有域函数必须设置为static类型,因为必须有了对象后才能调用函数,但是要调用了此函数才能创建对象,就会出现先有鸡还是先有蛋的问题,所以设置为static后,可以用类域调用! 方法二将析构函数封掉。 先解释为什么这个方法可行。一般来说C是静态绑定的语言在编译时期所有的非虚函数调用都必须分析完成。则当对象建立在栈上面时是由编译器分配内存空间的调用构造函数来构造栈对象。当对象使用完后编译器会调用析构函数来释放栈对象所占的空间。编译器管理了对象的整个生命周期具体过程为 通过直接移动栈顶指针挪出适当的空间然后在这片内存空间上调用构造函数形成一个栈对象。使用这种方法直接调用类的构造函数 。所以编译器在为类对象分配栈空间时会先检查类的析构函数的访问性其实不光是析构函数只要是非静态的函数编译器都会进行检查。如果类的析构函数是私有的则编译器不会在栈空间上为类对象分配内存。 所以 析构函数私有化的类的设计可以保证只能用new命令在堆(heap)中创建对象。 //封析构函数
class HeapOnly
{
public:void destory(){delete this;}
private:~HeapOnly(){cout调用析构成功!endl;}
};4.设计只能在栈上创建对象的类
只能在栈上创建对象的话那么就必须把new 和delete函数给封掉。因为new这个函数创建的对象一定是在堆上的。那其他的设计思路就和上述设计只能在堆上创建对象类似了
class StackOnly
{
public:static StackOnly CreateObj(){return StackOnly();}// 禁掉operator new可以把下面用new 调用拷贝构造申请对象给禁掉
// StackOnly obj StackOnly::CreateObj();
// StackOnly* ptr3 new StackOnly(obj);void* operator new(size_t size) delete;void operator delete(void* p) delete;
private:StackOnly() :_a(0){}private:int _a;
};5.只能实例化一个对象的类
一个类只能实例化出一个对象这就是大名鼎鼎的“单例模式”。
在阐述单例模式之前我们先需要了解一下什么是设计模式。 什么是单例模式呢 单例模式的运用是非常的广泛的如在线程池里面等等可以自行去搜索了解。
单例模式有两种实现的方式1 .饿汉模式就是说不管你将来用不用程序启动时就创建一个唯一的实例对象
2.懒汉模式只有在使用的时候才初始化出对象。
6.饿汉模式
注意,这里实现的是样例(demo)代码,在 不同的工程场景下需要大家做灵活的变换
在设计之前先明确的是什么情况下才会只初始化出一个对象呢不管其他在怎么做都不会初始化其他的对象。---静态成员函数及变量。只会初始化一次。---于是单例模式就可以设计成静态函数。
// 饿汉模式
// 优点简单
// 缺点可能会导致进程启动慢且如果有多个单例类对象实例启动顺序不确定。
class Singleton
{
public:static Singleton* GetInstance(){return _ins;}
private://限制类外随意创建对象Singleton(const Singleton s) delete;Singleton operator(const Singleton s) delete;Singleton(){}
private:static Singleton* _ins;
};
Singleton* Singleton::_ins new Singleton;注意类里面的静态成员变量只能在类中声明定义必须在类的外面。 单例模式的饿汉模式中,程序一启动就会把_ins,也就是唯一的实例对象给初始化, 并且由于构造函数被私有了,只能调用共有的GetInstance()函数获取_ins对象,又 由于这个对象是static类型的,所以不管你调用多少次GetInstance()都获取的是同 一个对象,也就是_ins 7.懒汉模式 如果单例对象构造十分耗时或者占用很多资源比如加载插件啊 初始化网络连接啊读取 文件啊等等而有可能该对象程序运行时不会用到那么也要在程序一开始就进行初始化 就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式延迟加载更好。 只有在使用的时候才进行初始化 //懒汉模式
class Singleton
{
public:static Singleton* GetInstance(){if (_ins nullptr){_ins new Singleton;}return _ins;}void DelInstance(){if (_ins ! nullptr){cout over!!! endl;delete _ins;_ins nullptr;}}
private://限制类外随意创建对象Singleton(const Singleton s) delete;Singleton operator(const Singleton s) delete;Singleton(){}
private:static Singleton* _ins;};
Singleton* Singleton::_ins nullptr;但是这样在多线程的情况下会有一个问题多个线程同时进入getinstance函数这就导致多个线程都生成了一个对象。这和我们要求的一个类只能创建一个对象时不符合的。所以说我们要对这种不是原子的操作进行加锁原子的操作学linux和mysql时会讲到 修改代码如下 //懒汉模式
class Singleton
{
public:static Singleton* GetInstance(){if (_ins nullptr)//双检查加锁,只有第一次进来时需要加锁,其他情况不用加锁{mtx.lock();if (_ins nullptr)//第一次调用才创建实例!{_ins new Singleton;}mtx.unlock();}return _ins;}void DelInstance(){mtx.lock();if (_ins ! nullptr){cout over!!! endl;delete _ins;_ins nullptr;}mtx.unlock();}
private://限制类外随意创建对象Singleton(const Singleton s) delete;Singleton operator(const Singleton s) delete;Singleton(){}
private:static Singleton* _ins;static mutex mtx;
};
Singleton* Singleton::_ins nullptr;
mutex Singleton::mtx;8.总结与拓展 特殊类的设计和单例模式就讲到这了只单说概念其实大家对于单例模式的强大性还是理解的不够深刻只有在结合项目一起使用时那么理解就会快速增加了。 拓展单例模式只是设计模式中的一种还有工厂模式适配器模式等等有想了解这些设计模式的可以自行查阅资料。
设计模式 - 概览 | C 全栈知识体系 (stibel.icu)