前言
在我们的学习中不免会遇到一些要设计一些特殊的类,要求这些类只能在内存中特定的位置创建对象,这就需要我们对类进行一些特殊的处理,那我们该如何解决呢?
目录
- 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&) = delete
char* _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++有了解了。
如果你感觉这篇博客对你有帮助,不要忘了一键三连哦