最近,我遇到了C++的Singleton设计模式的实现/实现。它看起来是这样的(我采用了现实生活中的例子):

// a lot of methods are omitted here
class Singleton
{
   public:
       static Singleton* getInstance( );
       ~Singleton( );
   private:
       Singleton( );
       static Singleton* instance;
};

从这个声明中,我可以推断实例字段是在堆上启动的。这意味着存在内存分配。对我来说完全不清楚的是,内存将在何时被释放?或者是否存在错误和内存泄漏?似乎在实现方面存在问题。

我的主要问题是,如何以正确的方式实现它?


当前回答

有人提到过std::call_once和std::once_flag吗?大多数其他方法——包括双重检查锁定——都是失败的。

单例模式实现中的一个主要问题是安全初始化。唯一安全的方法是用同步屏障保护初始化序列。但这些障碍本身需要安全启动。std::once_flag是保证安全初始化的机制。

其他回答

除了这里的其他讨论之外,可能值得注意的是,您可以使用全局性,而不限于一个实例。例如,考虑引用计数的情况。。。

struct Store{
   std::array<Something, 1024> data;
   size_t get(size_t idx){ /* ... */ }
   void incr_ref(size_t idx){ /* ... */}
   void decr_ref(size_t idx){ /* ... */}
};

template<Store* store_p>
struct ItemRef{
   size_t idx;
   auto get(){ return store_p->get(idx); };
   ItemRef() { store_p->incr_ref(idx); };
   ~ItemRef() { store_p->decr_ref(idx); };
};

Store store1_g;
Store store2_g; // we don't restrict the number of global Store instances

现在,在函数(如main)中的某个位置,您可以执行以下操作:

auto ref1_a = ItemRef<&store1_g>(101);
auto ref2_a = ItemRef<&store2_g>(201); 

ref不需要将指针存储回各自的store,因为这些信息是在编译时提供的。您也不必担心Store的生存期,因为编译器要求它是全局的。如果确实只有一个Store实例,那么这种方法没有开销;在不止一个实例的情况下,编译器需要对代码生成进行巧妙处理。如果需要,ItemRef类甚至可以成为Store的朋友(您可以有模板朋友!)。

如果Store本身是一个模板化的类,那么事情就变得更糟了,但是仍然可以使用这个方法,也许可以通过实现具有以下签名的helper类:

template <typename Store_t, Store_t* store_p>
struct StoreWrapper{ /* stuff to access store_p, e.g. methods returning 
                       instances of ItemRef<Store_t, store_p>. */ };

用户现在可以为每个全局Store实例创建StoreWrapper类型(和全局实例),并始终通过其包装器实例访问商店(从而忘记使用Store所需的模板参数的详细信息)。

接受答案中的解决方案有一个明显的缺点——在控件离开main()函数后调用单例的析构函数。当一些依赖对象被分配到main中时,可能真的会有问题。

我在尝试在Qt应用程序中引入Singleton时遇到了这个问题。我决定,我的所有设置对话框都必须是Singleton,并采用了上面的模式。不幸的是,Qt的主类QApplication被分配在主函数的堆栈上,当没有应用程序对象可用时,Qt禁止创建/销毁对话框。

这就是为什么我更喜欢堆分配的单件。我为所有单例提供了显式的init()和term()方法,并在main内部调用它们。因此,我可以完全控制单体创建/销毁的顺序,而且我保证无论是否有人调用getInstance(),都会创建单体。

另一个非分配的选择:根据需要创建一个单例,比如C类:

singleton<C>()

使用

template <class X>
X& singleton()
{
    static X x;
    return x;
}

在当前C++中,这一点和Cætælin的答案都不是自动线程安全的,而是在C++0x中。

上面链接的论文描述了双重检查锁定的缺点,即编译器可以在调用对象的构造函数之前为对象分配内存并设置指向分配内存地址的指针。在c++中,使用分配器手动分配内存,然后使用构造调用初始化内存是非常容易的。使用这种方法,双重检查锁定工作正常。

您的代码是正确的,只是没有在类外部声明实例指针。静态变量的类内声明在C++中不被视为声明,但在其他语言(如C#或Java等)中允许这样做。

class Singleton
{
   public:
       static Singleton* getInstance( );
   private:
       Singleton( );
       static Singleton* instance;
};
Singleton* Singleton::instance; //we need to declare outside because static variables are global

您必须知道Singleton实例不需要我们手动删除。我们需要在整个程序中使用它的一个对象,因此在程序执行结束时,它将被自动释放。