作者:知乎用户
链接:https://www.zhihu.com/question/27704562/answer/37760739
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
不使用编译器扩展,不用C++11,不加锁,也不使用原子操作的话,
那必须有个条件就是main函数执行之前程序必须是单线程的,不然真不行,再百度也不行,
如果有符合以上条件的单例类且不要求main之前是单线程的,请告诉我....然后:
这是2b单例:template<typename T>
class Im2Bsingleton
{
public:
static T* get() {
if(ptr==nullptr) { ptr=new T; }
return ptr;
};
private:
static T* ptr;
};
这个显然不是线程安全的,因为ptr会在第一次被使用的时候new T,
而第一次被使用的时候8成是main之后了,就很可能是多线程情况下,
那么可以使用一个原子操作:template<typename T>
class AtomicSingleton
{
public:
static T* get() {
for(;;) {
T* k=nullptr;
ptr.compare_exchange_weak(k,(T*)&place_holder);
if(k==0){
k=new T;
ptr.set(k);//此处必然成功
return k;
} else if(k==(T*)&place_holder) {
//别的线程占坑了,等着去吧!
} else {
return k;
}
}
};
private:
static atomic<T*> ptr;
static char place_holder;
};
代价是一次CAS和一次判断,但是题目说了不能加锁或者原子操作,那么,
再说一遍就是必须保证main函数执行之前不能存在多线程情况,
可以这样:template<typename T>
class Singleton
{
public:
static T* get() {
return ptr;
};
private:
static T* ptr;
};
你肯定觉得这个不对啊,ptr没被构造啊,对,保证前提的基础上,
我们只需要保证ptr能在main之前绝逼被构造就行了,
我们需要一个helper:普通单例
:template<typename T>
class Singleton
{
private:
struct ShitCleanHelper {
ShitCleanHelper(){ Singleton<T>::ptr=new T; }
~ShitCleanHelper(){
delete Singlenton<T>::ptr;
Singlenton<T>::ptr=nullptr;
}
};
public:
static T* get() {
return ptr;
};
private:
static T* ptr;
static ShitCleanHelper helper;
friend class ShitCleanHelper;
};
由于helper是static的,根据C++标准,静态对象被保证在main之前构造,
因此ShitCleanHelper被绝对保证在main之前构造,它在构造时候不干任何事,
只负责new T.这样,当进入main之后,ptr肯定就指着正确的对象了.
你会说那又不对了,这样虽然能够保证Singlenton在main之前new,
但是万一我需要在main之前就要用get()怎么办?
又或者,我的模块与模块之间不同的static对象互相有交互,这一切都发生在main之前,怎么办?
下面是文艺单例:template<typename T,bool DirectConstruction = true>
class ScopedSingleton
{
private:
class InstanceCreator {
public:
InstanceCreator() :m_ptr(new T) {};
~InstanceCreator() { delete m_ptr; };
operator T&() noexcept { return *m_ptr;}
private:
T* RESTRICT const m_ptr;
};
class DummyInstanceUser
{
public:
DummyInstanceUser(){ ScopedSingleton::getInstance(); }
~DummyInstanceUser() { ScopedSingleton::getInstance(); }
void DoNothing(){}
};
public:
static T& getInstance()
{
static InstanceCreator m_instancePtr;
m_dummyUser.DoNothing();
//此处似乎是模板的一个bug,加了这一句才能保证main之前m_instancePtr一定被构造.
return m_instancePtr;
};
private:
ScopedSingleton(){ getInstance(); }
~ScopedSingleton(){ getInstance(); }
private:
static DummyInstanceUser m_dummyUser;
};
首先,C++保证函数内的static变量保证在它第一次被使用之前被构造,
这就避免了"普通单例"中可能在ShitCleanHelper构造new T之前就要使用的尴尬局面.
无论什么时候要用它,它肯定会在这之前被构造了,但是这样一来又有问题了,
假如main函数之前谁都不用它,而main之后才有人第一次用到它,
那还怎么保证它在main之前被构造以避免多线程问题呢?
这就需要DummyInstanceUser出现了, DummyInstanceUser存在的唯一一个实例是m_dummyUser,
由于它是static的,所以它保证了在main函数之前被构造,而它的构造函数的唯一目的就是"假装使用了m_instancePtr",
这样即使没有人再main之前调用getInstance(),DummyUser依然会作为main之前的最后一道屏障保证在main之前一定构造成功.同时,这种单例还避免了跨模块的static变量互相引用的问题,例如有不同的单例A和B,它们在两个不同的cpp中,则它们的构造顺序虽然都在main之前,但互相之间的顺序是不确定的,但是使用这种文艺单例,如果A需要用到B,请看这一段文字的第一句话,只要A会用到B,B就会保证在A之前构造.
链接:https://www.zhihu.com/question/27704562/answer/37760739
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
不使用编译器扩展,不用C++11,不加锁,也不使用原子操作的话,
那必须有个条件就是main函数执行之前程序必须是单线程的,不然真不行,再百度也不行,
如果有符合以上条件的单例类且不要求main之前是单线程的,请告诉我....然后:
这是2b单例:template<typename T>
class Im2Bsingleton
{
public:
static T* get() {
if(ptr==nullptr) { ptr=new T; }
return ptr;
};
private:
static T* ptr;
};
这个显然不是线程安全的,因为ptr会在第一次被使用的时候new T,
而第一次被使用的时候8成是main之后了,就很可能是多线程情况下,
那么可以使用一个原子操作:template<typename T>
class AtomicSingleton
{
public:
static T* get() {
for(;;) {
T* k=nullptr;
ptr.compare_exchange_weak(k,(T*)&place_holder);
if(k==0){
k=new T;
ptr.set(k);//此处必然成功
return k;
} else if(k==(T*)&place_holder) {
//别的线程占坑了,等着去吧!
} else {
return k;
}
}
};
private:
static atomic<T*> ptr;
static char place_holder;
};
代价是一次CAS和一次判断,但是题目说了不能加锁或者原子操作,那么,
再说一遍就是必须保证main函数执行之前不能存在多线程情况,
可以这样:template<typename T>
class Singleton
{
public:
static T* get() {
return ptr;
};
private:
static T* ptr;
};
你肯定觉得这个不对啊,ptr没被构造啊,对,保证前提的基础上,
我们只需要保证ptr能在main之前绝逼被构造就行了,
我们需要一个helper:普通单例
:template<typename T>
class Singleton
{
private:
struct ShitCleanHelper {
ShitCleanHelper(){ Singleton<T>::ptr=new T; }
~ShitCleanHelper(){
delete Singlenton<T>::ptr;
Singlenton<T>::ptr=nullptr;
}
};
public:
static T* get() {
return ptr;
};
private:
static T* ptr;
static ShitCleanHelper helper;
friend class ShitCleanHelper;
};
由于helper是static的,根据C++标准,静态对象被保证在main之前构造,
因此ShitCleanHelper被绝对保证在main之前构造,它在构造时候不干任何事,
只负责new T.这样,当进入main之后,ptr肯定就指着正确的对象了.
你会说那又不对了,这样虽然能够保证Singlenton在main之前new,
但是万一我需要在main之前就要用get()怎么办?
又或者,我的模块与模块之间不同的static对象互相有交互,这一切都发生在main之前,怎么办?
下面是文艺单例:template<typename T,bool DirectConstruction = true>
class ScopedSingleton
{
private:
class InstanceCreator {
public:
InstanceCreator() :m_ptr(new T) {};
~InstanceCreator() { delete m_ptr; };
operator T&() noexcept { return *m_ptr;}
private:
T* RESTRICT const m_ptr;
};
class DummyInstanceUser
{
public:
DummyInstanceUser(){ ScopedSingleton::getInstance(); }
~DummyInstanceUser() { ScopedSingleton::getInstance(); }
void DoNothing(){}
};
public:
static T& getInstance()
{
static InstanceCreator m_instancePtr;
m_dummyUser.DoNothing();
//此处似乎是模板的一个bug,加了这一句才能保证main之前m_instancePtr一定被构造.
return m_instancePtr;
};
private:
ScopedSingleton(){ getInstance(); }
~ScopedSingleton(){ getInstance(); }
private:
static DummyInstanceUser m_dummyUser;
};
首先,C++保证函数内的static变量保证在它第一次被使用之前被构造,
这就避免了"普通单例"中可能在ShitCleanHelper构造new T之前就要使用的尴尬局面.
无论什么时候要用它,它肯定会在这之前被构造了,但是这样一来又有问题了,
假如main函数之前谁都不用它,而main之后才有人第一次用到它,
那还怎么保证它在main之前被构造以避免多线程问题呢?
这就需要DummyInstanceUser出现了, DummyInstanceUser存在的唯一一个实例是m_dummyUser,
由于它是static的,所以它保证了在main函数之前被构造,而它的构造函数的唯一目的就是"假装使用了m_instancePtr",
这样即使没有人再main之前调用getInstance(),DummyUser依然会作为main之前的最后一道屏障保证在main之前一定构造成功.同时,这种单例还避免了跨模块的static变量互相引用的问题,例如有不同的单例A和B,它们在两个不同的cpp中,则它们的构造顺序虽然都在main之前,但互相之间的顺序是不确定的,但是使用这种文艺单例,如果A需要用到B,请看这一段文字的第一句话,只要A会用到B,B就会保证在A之前构造.