做个工程上能用的单例模式,不仅要支持多线程,也要支持多参数传递,也要支持饿汉和懒汉两种模式,用了下c++11的call_once, lamda表达式,可变参数模板和forward的转发,代码如下:
#include <iostream>
#include <mutex>
using namespace std;
template<class T>
class Singleton
{
public:
template<class... Params>
static void init(Params&&... params)
{
if(ins == NULL)
ins=new T(std::forward<Params>(params)...);
}
static T* get()
{
if(ins == NULL)
{
cout<<"please init first."<<endl;
return ins;
}
return ins;
}
template<class... Params>
static T* getAndInit(Params&&... params)
{
static std::once_flag oc;//用于call_once的局部静态变量
std::call_once(oc, [&]{ins = new T(std::forward<Params>(params)...);});
return ins;
}
static void destroy()
{
delete ins;
ins=NULL;
}
private:
Singleton();
virtual ~Singleton();
Singleton(const Singleton& s);
Singleton& operator=(const Singleton& s);
static T* ins;
};
template<class T> T* Singleton<T>::ins=NULL;
其中init和get分离的是饿汉模式,在多线程程序中,潜在出现竞争的是new的地方,将init放到一个安全的地方,比如主进程的初始化部分,可以避免竞争。
getAndInit是典型的懒汉模式,即在get的时候检查初始化,传统的处理方法是double check+内存栅栏,不过既然c++11提供了call_once这么适合单例的操作,不用就可惜了,call_once的实际工作用的是lamda表达式。
template<class... Params> 比传统的template多了3个点,实现了可变参数模板,forward直接把可变的参数完美转发了。
Singleton的private部分是为了禁止构造,析构,拷贝构造和赋值,多说一句virtual修饰析构,是为了调用派生类和基类的析构函数,应对了动态绑定,预防了内存泄漏。
在函数外的ins赋值为NULL的部分,完成对ins的定义,这里需要在template之后明确的指定类型为T*。
附一个测试例子,注释掉的部分是测试饿汉的部分。
struct A
{
int a;
A(int val)
{
a=val;
cout<<"A is construct"<<endl;
}
};
int main()
{
// Singleton<A>::init(21);
// A* test1=Singleton<A>::get();
// cout<<test1<<endl;
// A* test2=Singleton<A>::get();
A* test1=Singleton<A>::getAndInit(32);
A* test2=Singleton<A>::getAndInit(34);
cout<<test1<<endl;
cout<<test2<<endl;
cout<<test2->a<<endl;
Singleton<A>::destroy();
return 0;
}
编译的方法是:
g++ test.cc -o test -std=c++11 -pthread
注意用到了call_once这个函数,所以编译的时候需要pthread。