实践1-设计模式-可以扩展的cpp多线程单例模式

可以扩展的多线程单例模式

单例模式真可谓非常出名了, 但是一般大家的探究仅仅止步于如何从多线程安全的角度来实现它.
我今天在网友的智慧的基础上往前再走一步, 提出可以支持扩展的单例模式.

在这篇文章中, 你将了解到4中单例模式, 它们层层递进. 它们分别是
(1) 隐藏构造函数的基本单例模式
(2) 支持多线程的单例模式
(3) 使用模板支持扩展的借助 static 成员属性的单例模式
(4) 使用模板支持扩展的借助 static 局部变量的单例模式

最后两个方法是我本人想了三个小时摸索出来的, 我还没有看到其他人有这么写过,所以保证是原创, 向毛主席保证.

下面我将逐步实现它们, 并且把代码展示出来, 在文章的末尾有代码的Github 链接.

(1) 隐藏构造函数的基本单例模式

// Created by zxzx on 2021/1/13.
//
class Singleton{
private:
	Singleton(){};
	static  Singleton *instance;

public:
	static getInstance(){
		if(instance== nullptr){
			instance = new Singleton();
		}
		return instance;
	};
};

Singleton* Singleton::instance = nullptr ;

这种就不用解释了, 经典中的经典.

(2) 支持多线程的单例模式

//
// Created by zxzx on 2021/1/13.
//

#include <mutex>
#include <iostream>
#include <thread>
#include <vector>
using namespace std;


class Singleton{
private:
	Singleton(){ // 这个方法导致Singleton无法被继承
		cout << "call constructor" << endl;
	};
	static Singleton *instance;
	static mutex m_mutex;

public:
	static Singleton* getInstance(){
		if(instance== nullptr){
			lock_guard<mutex> guard(m_mutex); // 使用lock_guard, 防止锁无法释放
			if(!instance){
				instance = new Singleton(); // 这里有一个问题:要不要把T的构造函数设置为私有的呢? 然后把 Singleton 类做为其友元?这里可以考虑如此设计。
			}
		}
		return instance;
	};

	static void delInstance(){
		if(instance!= nullptr){
			lock_guard<mutex> guard(m_mutex); // 使用lock_guard, 防止被多次释放
			if(instance){
				delete instance;
				instance = nullptr;
				cout << "deconstruct instance" << endl;
			}
		}
	}
};


Singleton* Singleton::instance = nullptr ;
mutex Singleton::m_mutex;

void f0(){
	Singleton* single = Singleton::getInstance();
	cout << single << endl; // 打印地址
}

void tets_multithread(){
	thread t1(f0);
	t1.join();
	thread t2(f0);
	t2.join();
	thread t3(f0);
	t3.join();
	thread t4(f0);
	t4.join();
	thread t5(f0);
	t5.join();

/** 打印结果如下:
 *
call constructor
0x7f8fc14029e0
0x7f8fc14029e0
0x7f8fc14029e0
0x7f8fc14029e0
0x7f8fc14029e0
 *
 * */
}

void f2(){
	Singleton* single = Singleton::getInstance();
	Singleton* single2 = Singleton::getInstance();
	cout << single << endl;
	cout << single2 << endl;
	Singleton::delInstance();
	single = Singleton::getInstance();
	cout << single << endl;
}


int main(){
	f2();
//	tets_multithread();
}

在这个例子中, 我们使用了 c++11的 mutex 类以及 lock_guard类, 可以保证 mutex 被及时的释放掉.
lock_guard 是一个位于 头文件中的类, 定义在 std namespace 中.

我们分别启动了五个线程测试,可以看到, 实例始终是一个对象,因为他们的地址相同.

(3) 使用模板支持扩展的借助 static 成员属性的单例模式

像上面的单例模式, 已经很接近完美了(忽略返回的是对象 还是指针, 忽略转移语义,拷贝函数等等C++语言细节)
但是有一个致命的问题就是, 它无法扩展, 如果我需要一个单例类,我需要从0开始设计, 每次都要写相同的代码,那么就非常的愚蠢了.

那么我们应该如何设计扩展可扩展的单例模式呢?

假设我的目标是这样子的, 我有一个类 A, 它是普通类, 然后我把它传入一个模板类 Singleton<T>里, 那么我们就拥有了一个单例类, Singleton<A>, 这样是不是非常的巧妙!!

所以基于刚才那个问题, 我考虑过几个问题:

(1)使用继承, 只要继承了这个单例类, 我们自己的类就是一个单例类, 这样听起来很棒, 也许可以尝试者实现一下, 我试过, 但是当时不知道怎么的卡主了, 就没往下走了.
我遇到的一个问题是: 因为我们把构造函数私有了之后, 这个类就没有办法被继承了, 所以继承这条路暂时走不通.

(2)使用模板, 或者说泛型编程(Java 方言) .
我的初步设计是这样的, 我们先写一个普通的类T(它不是单例模式的类, 可以自由的创建实例),然后把这个类传到模板类 TMP 里面去, 然后让这个模板类TMP返回一个 T类型的指针.
具体实现把我们自己的类 T 作为一个static 的成员属性,这样就支持扩展了.

这里有个难题是, 传入的类能当做静态属性吗? 后来我找了网上的例子,发现确实可以, 尝试了好久, 才最终写出这样的代码.

我的第一版代码长这样

#include <mutex>
#include <iostream>
#include <thread>
#include <vector>
using namespace std;

template <typename T>
class Singleton{
private:
	static mutex m_mutex;
	Singleton(){};
	Singleton(Singleton& other);
	Singleton& operator=(Singleton& other);
	static T * instance;

public:
	static T* getInstance(){ //这里应该返回
		if(!instance){
			lock_guard<mutex> guard(m_mutex); // 使用lock_guard, 防止锁无法释放
			if(!instance){
				instance = new T(); // 这里有一个问题:要不要把T的构造函数设置为私有的呢? 然后把 Singleton 类做为其友元?这里可以考虑如此设计。
			}
		}
		return instance;
	};
};

template <typename T>
mutex Singleton<T>::m_mutex; //

template <typename T>
T* Singleton<T>::instance;

测了了一下


class A{
public:
	int i1 = 1;

	void f1(){
		std::cout << "class A instance: " << this << std::endl;
	}
};

class B{
public:
	int i1 = 1;

	void f1(){
		std::cout << "class B instance: " << this << std::endl;
	}
};

void f0(){
	A* single = Singleton<A>::getInstance();
	single->f1();

	A* single2 = Singleton<A>::getInstance();
	single2->f1();

	A* a = new A();
	a->f1();

	B* single3 = Singleton<B>::getInstance();
	single3->f1();

}

int main(){
//	f2();
	f0();

}

// class A instance: 0x7fc27fc02990
// class A instance: 0x7fc27fc02990
// class A instance: 0x7fc27fc029a0 // 自己也可以新建类 ,后缀 a0 != 90
// class B instance: 0x7fc27fc029b0

这种方法是有问题的, 有什么问题呢?

(1) 返回我们自己传入的类的指针, 那么万一被用户错误的释放了怎么办? 比如 delete.
那么如果这个类自己调用 new ,返回的指针类型和这个单例模式返回的类岂不是一样的了!!

所以, 我们要返回一个 Singleton 类型的指针. 我们对代码做如下修改

//
// Created by zxzx on 2021/1/13.
//

#include <mutex>
#include <iostream>
#include <thread>
#include <vector>
using namespace std;

template <typename T>
class Singleton{
private:
	static mutex m_mutex;
	Singleton(){};
	Singleton(Singleton& other);
	Singleton& operator=(Singleton& other);

public:
	T t; // 我们自己的类, 嵌入在这个Singleton类中, 这里需要考虑的是构造传参问题, 可以考虑设计成指针, 从而更好地控制构造参数的调用
	static Singleton* instance;

	static Singleton* getInstance(){ //这里应该返回
		if(!instance){
			lock_guard<mutex> guard(m_mutex); // 使用lock_guard, 防止锁无法释放
			if(!instance){
				instance = new Singleton(); 
			}
		}
		return instance;
	};

};
template <typename T>
mutex Singleton<T>::m_mutex; // 泛型是也是可以使用静态成员的,只不过在外面初始化属性的时候要带上泛型定义

template <typename T>
Singleton<T>* Singleton<T>::instance;

测试一下,

class A{
public:
	int i1 = 1;

	void f1(){
		std::cout << "class A instance: " << this << std::endl;
	}
};

class B{
public:
	int i1 = 1;

	void f1(){
		std::cout << "class B instance: " << this << std::endl;
	}
};

void f0(){
	Singleton<A>* single = Singleton<A>::getInstance();
	single->t.f1();

	Singleton<A>* single2 = Singleton<A>::getInstance();
	single2->t.f1();

	Singleton<B>* singleb = Singleton<B>::getInstance();
	singleb->t.f1();

	Singleton<B>* singleb2 = Singleton<B>::getInstance();
	singleb2->t.f1();
}

int main(){
	f0();

}
/*
class A instance: 0x7ffc18402990
class A instance: 0x7ffc18402990
class B instance: 0x7ffc184029a0
class B instance: 0x7ffc184029a0
 *
 * */

这里有一个问题:要不要把T的构造函数设置为私有的呢? 然后把 Singleton 类做为其友元?这里可以考虑设计一下,
因为我们毕竟设计了一个类, 我们只想把它当做单例模式用, 不希望它可以被自由的创建实例, 所以我们可以写成这样

class A{
private:
    ~A(){} // 代表各种构造函数, 包括拷贝构造
    friend Singleton; // 把单例模式作为其you元类
public:
	int i1 = 1;

	void f1(){
		std::cout << "class A instance: " << this << std::endl;
	}
};

(4) 使用模板支持扩展的借助 static 局部变量的单例模式

这里用个小trick, 只适合C/C++, 因为他们有 static 临时变量(好骚的语法)

//
// Created by zxzx on 2021/1/13.
//
// 单线程版本的

#include <mutex>
#include <iostream>
#include <thread>
#include <vector>
using namespace std;

template <typename T>
class Singleton{
private:
	static mutex m_mutex;
	Singleton(){};
	Singleton(Singleton& other);
	Singleton& operator=(Singleton& other);

public:
	T t;
	static Singleton* getInstance(){ //这里应该返回
		static Singleton* instance;
		if(!instance){
			lock_guard<mutex> guard(m_mutex); // 使用lock_guard, 防止锁无法释放
			if(!instance){
				instance = new Singleton(); 
			}
		}
		return instance;
	};

};

template <typename T>
mutex Singleton<T>::m_mutex; 

测试代码如下:

class A{
public:
	int i1 = 1;

	void f1(){
		std::cout << "class A instance: " << this << std::endl;
	}
};

class B{
public:
	int i1 = 1;

	void f1(){
		std::cout << "class B instance: " << this << std::endl;
	}
};


void f0(){
	Singleton<A>* single = Singleton<A>::getInstance();
	single->t.f1();

	Singleton<A>* single2 = Singleton<A>::getInstance();
	single2->t.f1();

	Singleton<B>* singleb = Singleton<B>::getInstance();
	singleb->t.f1();

	Singleton<B>* singleb2 = Singleton<B>::getInstance();
	singleb2->t.f1();
}

int main(){
//	f2();
	f0();

}
/*
class A instance: 0x7ffc18402990
class A instance: 0x7ffc18402990
class B instance: 0x7ffc184029a0
class B instance: 0x7ffc184029a0
 *
 * */

总结

我们实现了4种单例模式, 后面两种是可以扩展的. 其核心思想是, 我们写一个类A, 可以通过模板 Singleton 来实现一个自动的单例类 Singleton<A>, 或者 Singleton<B>, 而A和B依然可以当做普通类来用, 如果介意这样, 还可以把它们的构造方法隐藏起来, 把 Singleton 类作为其友元, 只能通过 Singleton<A/B> 来获得单例类.

具体的做法是, 先使用普通的单例模式, 然后把这种模式变成一个模板类, 给模板类里面增加一个我们的类的成员属性, 然后通过模板类本身的单例来获取我们的自己的类的单例. 单例是可以传播的, 原理就是这么简单.

希望对大家有帮助.

代码地址: https://github.com/notfresh/cpp_learn/tree/master/designPattern

参考

[1]单例模式 冯Jungle
https://blog.youkuaiyun.com/sinat_21107433/article/details/102649056

[2]C++/Java__中类模板中的静态成员变量和静态成员函数
https://blog.youkuaiyun.com/u010003835/article/details/49104989

[3]C++中的单例模式
https://blog.youkuaiyun.com/hackbuteer1/article/details/7460019

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值