前言
在我们的学习中不免会遇到一些要设计一些特殊的类,要求这些类只能在内存中特定的位置创建对象,这就需要我们对类进行一些特殊的处理,那我们该如何解决呢?
目录
- 1. 特殊类的设计
- 1.1 设计一个类,不能被拷贝:
- 1.2设计一个类,只能在堆上创建对象
- 方法二:
- 1.3 设计一个类,只能在栈上创建对象
- 1.4 请设计一个类,不能被继承:
- 2 单例模式:
- 2.1 饿汉模式:
- 2.2 懒汉模式:
- 2.3 特殊情况下,单例类的释放
1. 特殊类的设计
1.1 设计一个类,不能被拷贝:
- 拷贝构造函数以及赋值运算符重载
- 只需让该类不能调用拷贝构造函数以及赋值运算符重载即可
C++98的处理方式:
-
让拷贝构造和赋值重载设成私有即可
-
只声明不定义。
-
C++11扩展delete的用法,delete除了释放new申请的资源外
-
如果在默认成员函数后跟上 = delete,表示让编译器删除掉该默认成员函数。
class CopyBan
{// ...CopyBan(const CopyBan&) = delete;CopyBan& operator=(const CopyBan&) = delete;// ...
};
1.2设计一个类,只能在堆上创建对象
C++98的设计的方法:
-
就直接将构造函数私有化
-
只声明不定义。
C++11的做法:
class HeapOnly
{
public:static HeapOnly* CreateObj(){//这里new直接去调用构造函数去了,类里面直接调用没限制return new HeapOnly;}//防拷贝,不让拷贝(形参可写可不写)HeapOnly(const HeapOnly&) = delete;//赋值并不会影响创建的对象跑到其他的地方去了,赋值不是创建对象的//拷贝构造反而是用来创建对象的private://构造函数私有 -- 将构造函数封起来(三条路都封死)HeapOnly(){}
};
问题:
- 那我们在类外调用CreateObj()来创建对象的时候,会有个问题
- 那就是著名的先有鸡还是先有蛋的问题
- 要调用成员函数,就要有对象,要有对象,要有对象,就需要先构造
- 我们要解决这个问题就必须打破这个循环死穴 我们将CreateObj()函数设成静态函数
补充:
- 普通的非静态的构造函数都需要对象去调用。
- 而构造函数不需要
此时还有个问题就是,拷贝构造也是会在栈上创建对象,我们可以将拷贝构造设成私有,或者C++11中直接将拷贝构造给删除掉。
方法二:
相比于上一种方法(将构造函数和拷贝构造私有或者删除),方法二显得更加牵强一点,将析构函数设成私有的,这样当对象生命周期结束时自动调用析构函数时会调用不到。
- 这种方式的缺陷是:new的时候确实能创建出来,但是在delete释放的时候会报错
解决办法:类外调用不了私有的析构函数,但是类内可以调用,所以我们可以提供一个接口
class HeapOnly
{
public:static void DelObj(HeapOnly* ptr){delete ptr;}//比较花的玩法 -- 这样还不用传参了/*void DelObj(){delete this; }*/private://析构函数私有~HeapOnly(){}
};int main()
{//HeapOnly h1;//static HeapOnly h2;HeapOnly* ph3 = new HeapOnly;//delete ph3;//方法一:要传参ph3->DelObj(ph3);//方法二:不用传参//ph3->DelObj();return 0;
}
1.3 设计一个类,只能在栈上创建对象
类似于上一个问题中的方法,我们先将三条路封死,然后单独给在栈上创建对象开条出路。
思路如下:
- 先将构造函数私有化
- 然后在类内写一个函数专门用来创建对象并负责将其传出来
- 注意:此时传返回值只能是传值返回,不能传指针,也不能传引用
- 因为是在栈上创建对象,是个局部变量,出了作用域就销毁掉了
class StackOnly
{
public:static StackOnly CreateObj(){//局部对象,不能用指针返回,也不能用引用返回,只能传值返回//传值返回必然会有一个拷贝构造发生return StackOnly();}void Print(){cout << "Stack Only" << endl;}
private://构造函数私有StackOnly(){}
};int main()
{StackOnly h1 = StackOnly::CreateObj();//不用对象去接受StackOnly::CreateObj().Print();//static StackOnly h2;//禁掉下面的玩法//StackOnly* ph3 = new StackOnly;return 0;
}
注意:
这里不能将拷贝构造给禁掉,因为CreateObj()是传值返回,传值返回必然会有一个拷贝构造(不考虑编译器优化的问题)。
1.4 请设计一个类,不能被继承:
- 构造函数私有
- 只声明不定义
class A
{
private:A(){}
};class B : public A
{};int main()
{B b;return 0;
}
- 父类A的构造函数私有化以后,B就无法构造对象
- 因为规定了子类的成员必须调用父类的构造函数初始化
这时又有一个问题 —— 先有鸡还是先有蛋的问题:
- 调用成员函数需要对象,对象创建需要调用成员函数,调用成员函数需要对象…
如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。
在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
2 单例模式:
概念:
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,一个进程只能有一个对象,并提供一个访问它的全局访问点,该实例被所有程序模块共享.
2.1 饿汉模式:
- 这种模式是不管你将来用不用这个类的对象,程序启动时就创建一个唯一的实例对象。
- 创建多线程都是在main函数之后创建的。(main函数之前没有多线程)。
%饿汉模式--一开始(main函数之前)就创建出对象
%优点:简单、没有线程安全问题
%缺点:1,一个程序中,多个单例,并且有先后创建初始化顺序要求时,饿汉无法控制比如程序中两个单例类A和B,假设要求A先创建初始化,B再创建初始化,2.饿汉单例类,初始化时任务多,会影响程序启动速度。class MemoryPool
{
public:static MemoryPool* GetInstance(){cout << _spInst << endl;return _spInst;}void Print();
private://构造函数私有化MemoryPool(){}//防不住拷贝,直接将其删除MemoryPool(const MemoryPool&) = delete;MemoryPool & operator=(MemoryPool const&) = deletechar* _ptr=nullptr; //声明static MemoryPool* _spInst; //声明
};void MemoryPool::Print()
{cout << *_ptr<< endl;
}MemoryPool* MemoryPool::_spInst = new MemoryPool; //定义int main()
{//GetInstance() 可以或者这个Singleton类的单例对象MemoryPool::GetInstance()->Print();//在外面就定义不出来对象了//MemoryPoolst1;//MemoryPool* st2 = new MemoryPool;//拷贝删除掉//MemoryPool copy(*MemoryPool::GetInstance());return 0;
}
2.2 懒汉模式:
- 开始不创建对象,在第一调用GetInstance()的时候再创建对象。
优点:
- 1.控制顺序
- 2.不影响启动速度。
缺点:
- 1.相对复杂(线程安全问题没讲)
- 2.线程安全问题要处理好
class Singleton
{
public:static Singleton* GetInstance() {**/ 注意这里一定要使用Double-Check的方式加锁,才能保证效率和线程安全**%一定要用双把锁if (nullptr == m_pInstance) {m_mtx.lock();if (nullptr == m_pInstance){m_pInstance = new Singleton();}m_mtx.unlock();}return m_pInstance;
}private:// 构造函数私有Singleton(){};// 防拷贝Singleton(Singleton const&);Singleton& operator=(Singleton const&);static Singleton* m_pInstance; // 单例对象指针static mutex m_mtx; //互斥锁
};Singleton* Singleton::m_pInstance = nullptr;
mutex Singleton::m_mtx
- 与饿汉模式不同的是, 懒汉是模式则是一开始不创建对象,而是一开始先给一个指针,这样就避免了程序启动起来很慢的问题。
思路:
- 将构造函数私有化
- 一开始不构建对象,只是给一个空指针
- 在需要的时候再调用其构造函数构造
补充:
- 懒汉是一种延迟加载,饿汉是一开始就加载
2.3 特殊情况下,单例类的释放
解决方案:
- 设计一个内部类
尾声
看到这里,相信大家对这个C++有了解了。
如果你感觉这篇博客对你有帮助,不要忘了一键三连哦