检查单例实例是否已实例化的正确方法是什么?

时间:2018-05-03 09:58:17

标签: c++ c++11 singleton

我见过以下两个例子:

class singleton {
    protected:
        static singleton *instance;
        singleton() { }
    public:
        static singleton *getInstance() {
            if (instance == 0)
                instance = new singleton();
            return instance;
        }
};
singleton *singleton::instance = 0; //This seems weird - why isn't nullptr being used ?

这个例子:

class Singleton
{
  private:

    static Singleton *p_inst;
    Singleton();

  public:

    static Singleton * instance()
    {
      if (!p_inst) // why isn't this being compared with nullptr ?
      {
        p_inst = new Singleton();
      }

      return p_inst;
    }
};

要检查实例是否已创建,为什么人们不会这样做:

class Singleton
{
  private:

    static Singleton *p_inst = nullptr;
    Singleton();

  public:

    static Singleton * instance()
    {
      if (p_inst != nullptr) // why isn't this being compared with nullptr ?
      {
        p_inst = new Singleton();
      }

      return p_inst;
    }
};

正确的方法是什么?

编辑:在下面的所有回复之后,我真的很困惑。什么是创建线程安全单例的明确方法,没有任何错误。

1 个答案:

答案 0 :(得分:0)

请注意,您列出的所有示例都没有实际尝试删除单例,并且它们都不是线程安全的,因为它们不会同步它们:

  • 与null(各种口味)比较
  • 他们对象的分配
  • 他们的指针分配

您现有的任何方法都无法安全地测试是否已创建单身。

C ++ 11为方法的静态数据成员添加了一个保证,即它的初始化是线程安全的 - 即只调用一次。 Tghis是推荐的机制。经典的单身人士就像:

SClass& Singleton()
{ 
  static SClass single(args);
  return single;
}

如果你真的想要对象分配堆:

SClass* Singleton()
{ 
  static std::unique_ptr<SClass> single (new SClass(args));
  return single;
}

[注意,如果您的对象构造不是一个简单的新对象,您可以在构造中使用函数调用和/或lambdas。您可以将lambdas编写为std :: unique_ptr的第二个参数以获得复杂的破坏,因此您可以在此模型中轻松自定义。 ]

如果你真的想在不使用静态成员的情况下这样做,你可以使用std :: mutex或std :: atomic with cmpexchange,但在c ++ 11中,静态成员可以为你工作。

此外,对象将被销毁,并且与它们的创建顺序相反。然而,这可能导致凤凰问题,SomeThing在破坏期间调用单例,并且它比你的单例更早构造,因此稍后会被破坏。

您正在查看的版本根本不会删除该对象,这将使valgrind非常不满意。如果你想要你的现有行为,使用线程安全的结构,但没有破坏,使用原始指针(它们没有析构函数):

SClass* Singleton()
{ 
  static SClass* single = new SClass(args);
  return single;
}

追溯修复是在构造过程中对单例进行SomeThing调用,或者在返回时能够处理nullptr。当然,您必须先调试该方案,然后才能修复它。所以我建议你至少在你的单身人士中添加一个异常终止,以便你知道它失败的原因: [实际上,正如评论中标记的那样,这并不是那么微不足道,因为unique_ptr在破坏后可能不会让自己处于良好的状态,所以我们需要一个自定义的ptr生命周期处理程序。幸运的是,我们在这里并不需要一个完整的unique_ptr,只需要3或4种方法。这可能会进入UB,因为编译器可以在销毁期间优化掉这个空赋值,或者DEADBEEF这个位置,但是不管怎样都不应该依赖于生产代码中的凤凰。]

SClass* Singleton()
{ 
  struct PUP: std::unique_ptr<SClass> single
  {
    typedef std::unique_ptr<SClass> base;
    PUP(SClass* s): base(s) {}
    ~PUP()  { operator =( base()_); }
  };
  static PUP single (new SClass(args));
  assert(single && "Singleton has been called in late static destructor - need phoenix");
  return single;
}

如果你愿意,你可以让物体从死里复活。这应该是安全的,因为静态破坏是单线程的。请注意,重新生成的对象永远不会被销毁,并且不会保留旧对象的任何状态。这对于日志记录类很有用,有人在析构函数中添加日志记录而没有意识到单例已被销毁的日志记录类。对于诊断,这很有用。对于生产代码,它仍然会让valgrind感到不安,只是不要这样做!

SClass* Singleton()
{ 
  static PUP single = new SClass(args);
  if (!single)
  {
    single = std::unique_ptr<SClass>(new SClass(args));
    // Hmm, and here is another phoenix being revived:
    Logger(CRITICAL) << " Revived singleton for class SClass!" << std::endl;
  }
  return single;
}