【C++】 单例设计模式的讲解

前言
在我们的学习中不免会遇到一些要设计一些特殊的类,要求这些类只能在内存中特定的位置创建对象,这就需要我们对类进行一些特殊的处理,那我们该如何解决呢?

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()
	{}
};

问题:

  1. 那我们在类外调用CreateObj()来创建对象的时候,会有个问题
  2. 那就是著名的先有鸡还是先有蛋的问题
  3. 调用成员函数,就要有对象,要有对象,要有对象,就需要先构造
  4. 我们要解决这个问题就必须打破这个循环死穴 我们将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++有了解了。
如果你感觉这篇博客对你有帮助,不要忘了一键三连哦

  • 22
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值