总结C++单例模式

单例模式介绍

单例模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式有三个关键点:

1、单例类只能有一个实例。
  为此,单例类只能提供私有的构造函数,即保证不能随意创建该类的实例。

2、单例类必须自己创建自己的唯一实例。
  因为构造函数是私有的,其他对象不能创建单例类的实例,只能是单例类自己来创建。

3、单例类必须给所有其他对象提供这一实例。
  外界需要获取并使用这个单例类的实例,但是由于该类的构造函数是私有的,外界无法通过new去获取它的实例,那么就必须提供一个静态的公有方法,该方法创建或者获取它本身的静态私有对象并返回。

单例类主要解决了一个全局使用的类的频繁的创建与销毁。

所以单例模式有以下几个优点: 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例;2、避免对资源的多重占用。

那单例模式有一个不好的地方就是,单例类没有接口,不能继承。

单例模式使用场景

单例模式就是一个类只能被实例化一次 ,也就是只能有一个实例化的对象的类。减少每次都new对象的开销,节省系统资源,同时也保证了访问对象的唯一实例。常用于如下场景:
1.频繁实例化然后销毁的对象。
2.创建对象太耗时,消耗太多资源,但又经常用到。

单例模式分为懒汉式和饿汉式两种。

懒汉式

懒汉式:故名思义,懒汉很懒只有饿了才会去找吃的。也就是说,只有在需要使用的时候才会去实例化。

线程不安全的懒汉式

代码:

#include <iostream>
#include <thread>
#include <mutex>

using namespace std;

//常规的懒汉式单例模式,存在线程安全问题
class Singleton2 {
public:
	static Singleton2* getInstance() {
		if (instance == NULL) {
			instance = new Singleton2();
		}
		return instance;
	}
	void test() {
		cout << "this is Singleton2..." << endl;
	}

private:
	Singleton2() { cout << "Singleton2构造函数" << endl; };	//构造函数私有化
	Singleton2(const Singleton2&) {}						//拷贝构造函数私有化
	Singleton2& operator=(const Singleton2&) {}				//赋值运算符私有化
	static Singleton2* instance;							//静态成员变量

	//类中嵌套类,用于自动回收资源
	class GC {
	public:
		~GC() {
			if (instance != NULL) {
				delete instance;
				instance = NULL;
				cout << "GC2自动回收..." << endl;
			}
			cout << "GC2..." << endl;
		}
	};

	static GC gc;
};
//静态成员变量,类内定义,类外初始化
Singleton2* Singleton2::instance = NULL;
Singleton2::GC Singleton2::gc;

int main() {
	Singleton2::getInstance()->test();

	return 0;
}

上面的代码是线程不安全的单例模式。因为,如果两个线程同时获取单例类的实例,都发现实例不存在,因此都会进行实例化,就会产生两个实例都要赋值给instance。

这里的线程安全指的是:一个线程在初始化某个变量的时候,其他线程执行到这个变量的初始化的时候,就会挂起。就是说只有一个线程会执行该变量的初始化

单例类中嵌套回收的类的作用是:不用再手动调用析构函数了。程序退出release对象析构时,单例的指针也会随着析构。

结果:
在这里插入图片描述

为了解决上述问题,设计出线程安全的懒汉式单例模式,需要考虑加锁。当然也可以不加锁,使用静态局部变量。
线程安全的懒汉式有两种实现方式,一种是使用静态局部变量,另一种是加锁。

使用静态局部变量(C++11)

由于C++11中规定静态成员变量就是线程安全的,所以这种写法不存在线程安全问题。
原理就是函数的局部静态变量生命周期随着进程结束而结束。

#include <iostream>
#include <thread>
#include <mutex>

using namespace std;

//使用静态局部变量的懒汉式单例模式
//由于C++11中规定静态成员变量就是线程安全的,所以这种写法不存在线程安全问题
class Singleton1 {
public:
	static Singleton1* getInstance() {
		static Singleton1 instance;
		return &instance;
	}

	void test() {
		cout << "this is Singleton1..." << endl;
	}

private:
	Singleton1() { cout << "Singleton1构造函数" << endl; }								//构造函数私有化
	Singleton1(const Singleton1&){}				//拷贝构造函数私有化
	Singleton1& operator=(const Singleton1&) {}	//赋值运算符私有化
};

int main() {
	Singleton1::getInstance()->test();	//ingleton1构造函数	  this is Singleton1...

	return 0;
}

结果:
在这里插入图片描述

加锁的懒汉式

#include <iostream>
#include <thread>
#include <mutex>

using namespace std;

//加锁,解决常规的懒汉式单例模式,存在线程安全的问题
class Singleton3 {
public:
	static Singleton3* getinstance() {
		//两个NULL可以提高获取实例效率
		if (instance == NULL) {
			mtx.lock();
			if (instance == NULL) {
				instance = new Singleton3();
			}
			mtx.unlock();
		}
		return instance;
	}

	void test() {
		cout << "this is Singleton3..." << endl;
	}

private:
	Singleton3() { cout << "Singleton3构造函数" << endl; }
	Singleton3(const Singleton3&) {}
	Singleton3& operator=(const Singleton3&) {}
	static Singleton3* instance;

	static mutex mtx;	//锁

	//类中嵌套类,用于自动回收资源
	class GC {
	public:
		~GC() {
			if (instance != NULL) {
				delete instance;
				instance = NULL;
				cout << "GC3自动回收..." << endl;
			}
			cout << "GC3..." << endl;
		}
	};
	static GC gc;
};
Singleton3* Singleton3::instance = NULL;
mutex Singleton3::mtx;
Singleton3::GC Singleton3::gc;

int main() {
	Singleton3::getinstance()->test();
	return 0;
}

结果:
在这里插入图片描述

饿汉式

饿汉式:饿了肯定要饥不择食。在单例类定义的时候就进行实例化。
饿汉式没有线程安全问题。
代码:

#include <iostream>
#include <thread>
#include <mutex>

using namespace std;

//饿汉式单例模式,不需要加锁,也不存在线程安全问题
class Singleton4 {
public:
	static Singleton4* getInstance() {
		return instance;
	}

	void test() {
		cout << "this is Singleton4..." << endl;
	}

private:
	Singleton4() { cout << "Singleton4构造函数" << endl; }
	Singleton4(const Singleton4&){}
	Singleton4& operator=(const Singleton4&) {}
	static Singleton4* instance;

	//类中嵌套类,用于自动回收
	class GC {
	public:
		~GC() {
			if (instance != NULL) {
				delete instance;
				instance = NULL;
				cout << "GC4自动回收..." << endl;
			}
			cout << "GC4..." << endl;
		}
	};
	static GC gc;
};
Singleton4* Singleton4::instance = new Singleton4();
Singleton4::GC Singleton4::gc;

int main() {
	Singleton4::getInstance()->test();

	return 0;
}

结果:
在这里插入图片描述

c++单例模式为什么不在析构函数中释放静态的单例对象

1、单例中的 new 的对象需要delete释放。

2、delete释放对象的时候才会调用对象的析构函数。

3、如果在析构函数里调用delete,那么程序结束时,根本进不去析构函数,怎么会delete。

4、如果程序结束能自动析构,那么就会造成一个析构的循坏,所以new对应于delete。

经过验证,如果不手动delete对象,符合第3条,因为只有进行了delete的时候才会执行析构函数,但是delete是在析构函数中执行的,不在外部手动delete,则不会执行析构函数,所以在析构函数中释放对象也没有用。

但是,如果手动delete对象,将会出现死循环,也就是出现了第4条的情况。因为手动delete会调用析构函数,而在析构函数中,又使用了delete静态的单例对象,进而又会调用析构函数,这样就会一直循环下去,直到栈溢出。

综上所述,不可以在析构函数中释放静态的单例对象。可以在类中嵌套一个回收的类,用于释放单例的对象。

如果不在析构函数中释放静态的单例对象,而在外部手动使用delete,会释放单例的对象吗?

答案:不会
例如如下代码:

	Singleton2* S2 = Singleton2::getInstance();
	cout << S2 << endl;	//016C5888
	delete S2;
	S2 = NULL;
	cout << S2 << endl;	//00000000
	cout << Singleton2::getInstance() << endl;//016C5888

结果:
在这里插入图片描述
从结果中可以看出,把Singleton2::getInstance()赋值给S2之后,S2就指向了单例对象的那块内存,但是
即使把S2 delete掉之后,单例对象还是存在的。这有点像与智能指针,总之,无法通过外部的delete删除单例对象,所以只能在单例类内部使用嵌套类来删除单例对象,防止内存泄漏。
参考链接:
C++ 单例模式 - 泣血 - 博客园 (cnblogs.com)

C++实现单例模式_Elena-N的博客-CSDN博客_单例模式c++实现

【设计模式】单例模式,嵌套类实现释放对象内存 - 走看看 (zoukankan.com)

C++单例模式为何要实例化一个对象不全部使用static_C 语言_脚本之家 (jb51.net)

C++单例模式 正确的资源回收方式_ice_ly000的博客-CSDN博客

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值