C++ Singleton设计模式

Art*_*ger 692 c++ singleton design-patterns

最近我碰到了C++的Singleton设计模式的实现/实现.看起来像这样(我从现实生活中采用了它):

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

从这个声明我可以推断出实例字段是在堆上启动的.这意味着存在内存分配.对我来说完全不清楚的是,什么时候内存将被解除分配?还是有漏洞和内存泄漏?好像在实施中存在问题.

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

Mar*_*ork 1035

在2008年,我提供了Singleton设计模式的C++ 98实现,它是懒惰评估,保证破坏,非技术上线程安全的:
任何人都可以在c ++中为我提供Singleton样本吗?

这是Singleton设计模式的更新C++ 11实现,它是惰性求值,正确销毁和线程安全的.

class S
{
    public:
        static S& getInstance()
        {
            static S    instance; // Guaranteed to be destroyed.
                                  // Instantiated on first use.
            return instance;
        }
    private:
        S() {}                    // Constructor? (the {} brackets) are needed here.

        // C++ 03
        // ========
        // Don't forget to declare these two. You want to make sure they
        // are unacceptable otherwise you may accidentally get copies of
        // your singleton appearing.
        S(S const&);              // Don't Implement
        void operator=(S const&); // Don't implement

        // C++ 11
        // =======
        // We can use the better technique of deleting the methods
        // we don't want.
    public:
        S(S const&)               = delete;
        void operator=(S const&)  = delete;

        // Note: Scott Meyers mentions in his Effective Modern
        //       C++ book, that deleted functions should generally
        //       be public as it results in better error messages
        //       due to the compilers behavior to check accessibility
        //       before deleted status
};
Run Code Online (Sandbox Code Playgroud)

请参阅此文章,了解何时使用单例:(不经常)
Singleton:如何使用它

请参阅这两篇关于初始化顺序以及如何应对的文章:
静态变量初始化顺序
查找C++静态初始化顺序问题

请参阅本文描述生命周期:
C++函数中静态变量的生命周期是多少?

请参阅本文讨论对单例的一些线程影响:
Singleton实例声明为GetInstance方法的静态变量,它是否是线程安全的?

看到这篇文章解释了为什么双重检查锁定不适用于C++:
C++程序员应该知道的所有常见的未定义行为是什么?
Dobbs博士:C++和双重锁定的危险:第一部分

  • @Varuna:在C++ 11中,这现在是线程安全的. (79认同)
  • 好答案.但请注意,这不是线程安全的http://stackoverflow.com/questions/1661529/is-meyers-implementation-of-singleton-pattern-thread-safe (20认同)
  • @zourtney:很多人都没有意识到你刚刚做了什么:) (4认同)
  • @MaximYegorushkin:当它被破坏时**非常明确**(没有歧义).请参阅:http://stackoverflow.com/questions/246564/what-is-the-lifetime-of-a-static-variable-in-ac-function (4认同)
  • §6.7 [stmt.dcl] p4 `如果在初始化变量时控制同时进入声明,则并发执行应等待初始化完成。` (3认同)
  • `最令我烦恼的是getInstance()中隐藏布尔值的运行时检查`这是对实现技术的假设.不需要假设它还活着.请参阅http://stackoverflow.com/a/335746/14065您可以强制某种情况使其始终存在(比"Schwarz计数器"更少的开销).由于您不强制执行订单,因此全局变量在初始化顺序(跨编译单元)方面存在更多问题.该模型的优点是1)延迟初始化.2)执行命令的能力(施瓦茨帮助但更加丑陋).是的`get_instance()`更加丑陋. (3认同)
  • @kol:不,这不是平常的.仅仅因为初学者不假思索地复制和粘贴代码就不会使它成为常用代码.您应该始终查看用例并确保赋值运算符执行预期的操作.复制和粘贴代码会导致您出错. (3认同)
  • @m4l490n 事实并非如此。但它表明您应该将单例的构造函数放在类的“private”部分中。这将防止意外构造该类的实例。 (3认同)
  • @fnieto:谢谢。我试图用构造函数 S() 暗示的内容应该声明为私有。由于单例将有其他需要初始化的成员(否则为什么有单例)(注意有 __not__ a 不要实现),所以当你声明一个构造函数时,它需要是私有的。 (2认同)
  • 上面已经提到:http://stackoverflow.com/questions/449436/singleton-instance-declared-as-static-variable-of-getinstance-method/449823#449823 (2认同)
  • @kol:“正确的返回类型”。您正在混淆术语。您指的是编译器提供的默认生成的赋值运算符。这不是正确的**。正确的赋值运算符取决于情况。并且在这种情况下使用`void`更正确。注意:任何赋值运算符都会阻止编译器生成默认值。 (2认同)
  • @MaximYegorushkin:Schwarts计数器总是初始化对象。以上仅在首次使用时初始化。 (2认同)
  • @StefanStanković 你可以。我不会。这里的问题是如果它是从多个位置调用的。第一个调用它的位置是传递参数的位置。所有其他调用参数都会掉在地板上并被忽略。**但是** 这也是为什么单例模式单独使用时不是一个好主意。您应该将此模式与构建器模式(例如工厂)结合使用。总的来说,单例模式不是一个好主意。您可能应该考虑另一种方法来设计您的应用程序。 (2认同)
  • @Ace 更喜欢引用(别名)而不是指针。这样就不会出现所有权混乱的情况。此外,“std::unique_ptr”没有任何开销,可以帮助您管理指针的生命周期。 (2认同)

Ree*_*sey 46

作为一个单身人士,你通常不希望它被破坏.

当程序终止时,它将被拆除并解除分配,这是单例的正常,期望的行为.如果你想能够明确地清理它,那么向类中添加一个静态方法是非常容易的,它允许你将它恢复到干净状态,并在下次使用它时重新分配它,但这超出了范围. "经典"单身人士.

  • 直言不讳......"记忆泄漏"与单身人士的关系完全无关紧要.如果你有解构秩序重要的有状态资源,单身人士可能是危险的; 但是在程序终止时操作系统将所有内存都清晰地重新获得......在99.9%的案例中取消了这个完全学术点.如果你想要反复讨论什么是"内存泄漏"而不是"内存泄漏"的语法,那很好,但要意识到它会分散实际的设计决策. (15认同)
  • @jkerian:C++上下文中的内存泄漏和破坏并不是关于内存泄漏的.真的是资源控制.如果泄漏内存,则不会调用destroctor,因此未正确释放与该对象关联的任何资源.内存只是我们在教授编程时使用的简单示例,但是有更复杂的资源. (12认同)
  • 它不是一个内存泄漏,而是一个简单的全局变量声明. (6认同)
  • @Martin我完全同意你的看法.即使唯一的资源是内存,如果您不得不浏览泄漏列表,过滤掉"无关紧要"的内容,您仍然会在程序中找到真正的泄漏时遇到麻烦.最好清理这些,以便报告泄漏的任何工具只报告有问题的事情. (6认同)
  • 如果从不在静态Singleton*实例上显式调用delete,那么技术上是否仍然会将其视为内存泄漏? (4认同)
  • 值得考虑的是,存在C++实现(可能甚至是托管的实现),其中"OS"*在您的程序退出时不会*恢复所有资源,但是它确实有一些"再次运行程序"的概念,它会给你一个全新的全局和静态本地人.在这样的系统上,任何合理的定义是一个真正的泄漏:如果你的程序运行足够多次,它将取消系统.你是否关心这种系统的可移植性是另一回事 - 只要你不写一个库,你几乎肯定不会. (3认同)
  • @DavidFrye 实际上,这是多个领域中的常见情况。例如,Windows 内核驱动程序在独立环境中运行,即在终止时不检索资源。几乎所有的嵌入式软件也是如此。 (3认同)

Căt*_*tiș 36

你可以避免内存分配.存在许多变体,在多线程环境的情况下都存在问题.

我更喜欢这种实现(实际上,我没有正确地说我更喜欢,因为我尽可能地避免单身人士):

class Singleton
{
private:
   Singleton();

public:
   static Singleton& instance()
   {
      static Singleton INSTANCE;
      return INSTANCE;
   }
};
Run Code Online (Sandbox Code Playgroud)

它没有动态内存分配.

  • 在某些情况下,这种延迟初始化不是理想的模式.一个例子是单例的构造函数是否从堆中分配内存,并且您希望该分配是可预测的,例如在嵌入式系统或其他严格控制的环境中.当Singleton模式是最佳模式时,我更喜欢将实例创建为类的静态成员. (3认同)
  • 对于许多大型程序,尤其是那些具有动态库的程序 由于库卸载时的破坏问题顺序,任何非原始的全局或静态对象都可能导致程序在许多平台上退出时发生段错误/崩溃.这是许多编码约定(包括谷歌)禁止使用非平凡的静态和全局对象的原因之一. (3认同)
  • @FaceBro 这里有两个关键字“static”的实例,但从链接的角度来看,它们都没有问题。“static”第一次出现是关于静态成员函数,与链接无关。‘static’的第二次出现是关于‘INSTANCE’的存储时长。该对象将在程序运行期间驻留在内存中,并且您无法通过 TU 外部的名称访问它并不重要,因为您可以通过具有外部链接的成员函数“instance”访问它。 (2认同)

Gal*_*lik 14

@Loki Astari的答案很棒.

但是,有时会出现多个静态对象,您需要能够保证在使用单例的所有静态对象不再需要它之前不会销毁单例.

在这种情况下,即使在程序结束时调用静态析构函数,std::shared_ptr也可以用于为所有用户保持单例存活:

class Singleton
{
public:
    Singleton(Singleton const&) = delete;
    Singleton& operator=(Singleton const&) = delete;

    static std::shared_ptr<Singleton> instance()
    {
        static std::shared_ptr<Singleton> s{new Singleton};
        return s;
    }

private:
    Singleton() {}
};
Run Code Online (Sandbox Code Playgroud)

  • @MohammedNoureldin 默认情况下,`C++` 将自动生成函数来复制对象。如果您想防止对象被复制,您可以“删除”这些功能。所以`=delete`告诉编译器不要生成它们。 (2认同)
  • 我已经开始使用这种方法来维护一些过于复杂(单一)的遗留软件。当我研究终止期间发生的神秘崩溃时,我发现有几个单例持有指向其他单例的指针,并在销毁期间调用它们的方法,导致整个静态去初始化顺序惨败。切换到这种方法消除了单例被运行时无序销毁时发生的释放后使用错误。它确实值得更多投票,谢谢! (2认同)

Jam*_*kin 9

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

singleton<C>()
Run Code Online (Sandbox Code Playgroud)

运用

template <class X>
X& singleton()
{
    static X x;
    return x;
}
Run Code Online (Sandbox Code Playgroud)

这个和Cătălin的答案在当前的C++中都不是自动线程安全的,而是在C++ 0x中.

  • 这种设计的问题在于如果跨多个库使用.每个库都有该库使用的单例的自有副本.所以它不再是单身人士了. (13认同)

Red*_*ave 8

有人提到过std::call_oncestd::once_flag吗?大多数其他方法 - 包括双重检查锁定 - 都被破坏了。

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


Tun*_*her 7

这是一个简单的实现。

#include <Windows.h>
#include <iostream>

using namespace std;


class SingletonClass {

public:
    static SingletonClass* getInstance() {

    return (!m_instanceSingleton) ?
        m_instanceSingleton = new SingletonClass : 
        m_instanceSingleton;
    }

private:
    // private constructor and destructor
    SingletonClass() { cout << "SingletonClass instance created!\n"; }
    ~SingletonClass() {}

    // private copy constructor and assignment operator
    SingletonClass(const SingletonClass&);
    SingletonClass& operator=(const SingletonClass&);

    static SingletonClass *m_instanceSingleton;
};

SingletonClass* SingletonClass::m_instanceSingleton = nullptr;



int main(int argc, const char * argv[]) {

    SingletonClass *singleton;
    singleton = singleton->getInstance();
    cout << singleton << endl;

    // Another object gets the reference of the first object!
    SingletonClass *anotherSingleton;
    anotherSingleton = anotherSingleton->getInstance();
    cout << anotherSingleton << endl;

    Sleep(5000);

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

只创建了一个对象,并且每次都返回此对象引用。

SingletonClass instance created!
00915CB8
00915CB8
Run Code Online (Sandbox Code Playgroud)

这里 00915CB8 是单例对象的内存位置,在程序运行期间相同,但(通常!)每次程序运行时都不同。

注意这不是线程安全的。你必须确保线程安全。


Sad*_*ido 6

接受的答案中的解决方案有一个明显的缺点 - 在控件离开main()函数后调用单例的析构函数.当内部分配一些依赖对象时,可能确实存在问题main.

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

这就是为什么我更喜欢堆分配的单例.我提供一个明确init()term()方法,对所有的单身人士和内给他们打电话main.因此,我可以完全控制单身人士创造/毁灭的顺序,而且我保证无论是否有人打电话都会创造单身人士getInstance().

  • 如果您指的是当前接受的答案,那么您的第一个陈述是错误的.在销毁所有静态存储持续时间对象之前,不会调用析构函数. (2认同)

rid*_*hap 5

如果要在堆中分配对象,为什么不使用唯一指针.由于我们使用唯一指针,因此内存也将被释放.

class S
{
    public:
        static S& getInstance()
        {
            if( m_s.get() == 0 )
            {
              m_s.reset( new S() );
            }
            return *m_s;
        }

    private:
        static std::unique_ptr<S> m_s;

        S();
        S(S const&);            // Don't Implement
        void operator=(S const&); // Don't implement
};

std::unique_ptr<S> S::m_s(0);
Run Code Online (Sandbox Code Playgroud)

  • 在c ++ 11中弃用.建议使用unique_ptr.http://www.cplusplus.com/reference/memory/auto_ptr/ http://www.cplusplus.com/reference/memory/unique_ptr/ (3认同)
  • 这不是线程安全的.最好使`m_s`成为`getInstance()`的局部`静态`,并在没有测试的情况下立即初始化它. (2认同)

Yur*_*riy 5

我没有在答案中找到CRTP实现,所以这里是:

template<typename HeirT>
class Singleton
{
public:
    Singleton() = delete;

    Singleton(const Singleton &) = delete;

    Singleton &operator=(const Singleton &) = delete;

    static HeirT &instance()
    {
        static HeirT instance;
        return instance;
    }
};
Run Code Online (Sandbox Code Playgroud)

要使用它,只需继承您的类,例如: class Test : public Singleton<Test>

  • 在我将默认构造函数设置为 protected 和 '= default;' 之前,无法让它与 C++17 一起使用。 (3认同)

Ton*_*Bai 5

We went over this topic recently in my EECS class. If you want to look at the lecture notes in detail, visit http://umich.edu/~eecs381/lecture/IdiomsDesPattsCreational.pdf. These notes (and quotations I give in this answer) were created by my Professor, David Kieras.

There are two ways that I know to create a Singleton class correctly.

First Way:

Implement it similar to the way you have it in your example. As for destruction, "Singletons usually endure for the length of the program run; most OSs will recover memory and most other resources when a program terminates, so there is an argument for not worrying about this."

However, it is good practice to clean up at program termination. Therefore, you can do this with an auxiliary static SingletonDestructor class and declare that as a friend in your Singleton.

class Singleton {
public:
  static Singleton* get_instance();
  
  // disable copy/move -- this is a Singleton
  Singleton(const Singleton&) = delete;
  Singleton(Singleton&&) = delete;
  Singleton& operator=(const Singleton&) = delete;
  Singleton& operator=(Singleton&&) = delete;

  friend class Singleton_destroyer;

private:
  Singleton();  // no one else can create one
  ~Singleton(); // prevent accidental deletion

  static Singleton* ptr;
};

// auxiliary static object for destroying the memory of Singleton
class Singleton_destroyer {
public:
  ~Singleton_destroyer { delete Singleton::ptr; }
};

// somewhere in code (Singleton.cpp is probably the best place) 
// create a global static Singleton_destroyer object
Singleton_destoyer the_destroyer;
Run Code Online (Sandbox Code Playgroud)

The Singleton_destroyer will be created on program startup, and "when program terminates, all global/static objects are destroyed by the runtime library shutdown code (inserted by the linker), so the_destroyer will be destroyed; its destructor will delete the Singleton, running its destructor."

Second Way

This is called the Meyers Singleton, created by C++ wizard Scott Meyers. Simply define get_instance() differently. Now you can also get rid of the pointer member variable.

// public member function
static Singleton& Singleton::get_instance()
{
  static Singleton s;
  return s;
}
Run Code Online (Sandbox Code Playgroud)

This is neat because the value returned is by reference and you can use . syntax instead of -> to access member variables.

"Compiler automatically builds code that creates 's' first time through the declaration, not thereafter, and then deletes the static object at program termination."

Note also that with the Meyers Singleton you "can get into very difficult situation if objects rely on each other at the time of termination - when does the Singleton disappear relative to other objects? But for simple applications, this works fine."


归档时间:

查看次数:

645119 次

最近记录:

6 年 前