求教一个百度的面试题,关于多线程单例模式的? - 知乎 - c++

不使用编译器扩展,不用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之前构造.


原网址: 访问
创建于: 2022-10-13 10:31:07
目录: default
标签: 无

请先后发表评论
  • 最新评论
  • 总共0条评论