C ++ Singleton设计模式

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

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

从该声明中,我可以推断出实例字段是在堆上初始化的。 这意味着存在内存分配。 对我来说,完全不清楚的是何时确切地将要释放内存? 还是有错误和内存泄漏? 似乎实现中存在问题。

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


#1楼

作为单例,您通常不希望其被破坏。

当程序终止时,它将被拆除并释放,这是单例的正常期望行为。 如果您希望能够显式清除它,则可以很容易地在类中添加一个静态方法,该方法允许您将其恢复为干净状态,并在下次使用时重新分配它,但这超出了范围。 “经典”单身人士。


#2楼

您可以避免分配内存。 有许多变体,在多线程环境中都存在问题。

我更喜欢这种实现方式(实际上,我没有正确说出我喜欢的方式,因为我尽可能避免单例):

class Singleton
{
private:
   Singleton();

public:
   static Singleton& instance()
   {
      static Singleton INSTANCE;
      return INSTANCE;
   }
};

它没有动态内存分配。


#3楼

它确实可能是从堆中分配的,但是没有源就无法知道。

典型的实现(取自我已经在emacs中拥有的一些代码)将是:

Singleton * Singleton::getInstance() {
    if (!instance) {
        instance = new Singleton();
    };
    return instance;
};

...并依靠程序超出范围进行清理。

如果您在必须手动执行清理的平台上工作,我可能会添加一个手动清理例程。

这样做的另一个问题是它不是线程安全的。 在多线程环境中,两个线程都可以通过“ if”,然后每个线程才有机会分配新实例(因此两者都可以)。 如果仍然要依靠程序终止来清理,那么这仍然不是什么大问题。


#4楼

另一个非分配的替代方法:根据需要创建一个单例,例如类C

singleton<C>()

使用

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

在当前C ++中,此答案和Cătălin的答案都不会自动实现线程安全,但在C ++ 0x中都不会。


#5楼

在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
};

请参阅本文,了解何时使用单例:(不常使用)
Singleton:应如何使用

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

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

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

请参阅这篇文章,解释为什么双重检查锁定在C ++上不起作用:
C ++程序员应该知道哪些常见的未定义行为?
Dobbs博士:C ++和双重检查锁定的风险:第一部分


#6楼

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

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

这就是为什么我更喜欢堆分配的单例。 我为所有单例提供了一个显式的init()term()方法,并将其称为main 。 因此,我可以完全控制单例创建/销毁的顺序,并且我保证无论是否有人调用getInstance()都可以创建单例。


#7楼

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

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);

#8楼

#define INS(c) private:void operator=(c const&){};public:static c& I(){static c _instance;return _instance;}

例:

   class CCtrl
    {
    private:
        CCtrl(void);
        virtual ~CCtrl(void);

    public:
        INS(CCtrl);

#9楼

这是一个简单的实现。

#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;
}

每次之后字后,仅返回一个创建的对象和该对象引用。

SingletonClass instance created!
00915CB8
00915CB8

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

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


#10楼

这是关于对象生命周期管理的。 假设您的软件中有多个单例。 它们取决于Logger单例。 在应用程序销毁期间,假设另一个单例对象使用Logger记录其销毁步骤。 您必须保证Logger应该最后清理。 因此,也请查看这篇文章: http : //www.cs.wustl.edu/~schmidt/PDF/ObjMan.pdf


#11楼

除了此处的其他讨论之外,可能值得注意的是,您可以具有全局性,而不会将用法限制为一个实例。 例如,考虑引用计数的情况...

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类成为Storefriend (您可以有模板化的朋友!)。

如果Store本身是模板化的类,那么事情会变得更加混乱,但是仍然可以使用此方法,也许可以通过实现具有以下签名的帮助器类:

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所需的模板参数的繁琐细节)。


#12楼

@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() {}
};

#13楼

我没有在答案中找到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;
    }
};

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


#14楼

有人提到过std::call_oncestd::once_flag吗? 大多数其他方法-包括双重检查锁定-都是无效的。

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


#15楼

简单的单例类,这必须是您的头文件

#ifndef SC_SINGLETON_CLASS_H
#define SC_SINGLETON_CLASS_H

class SingletonClass
{
    public:
        static SingletonClass* Instance()
        {
           static SingletonClass* instance = new SingletonClass();
           return instance;
        }

        void Relocate(int X, int Y, int Z);

    private:
        SingletonClass();
        ~SingletonClass();
};

#define sSingletonClass SingletonClass::Instance()

#endif

像这样访问您的单身人士:

sSingletonClass->Relocate(1, 2, 5);

#16楼

我的实现类似于Galik的实现。 区别在于我的实现允许共享指针清除分配的内存,而不是保持内存直到应用程序退出并清除静态指针。

#pragma once

#include <memory>

template<typename T>
class Singleton
{
private:
  static std::weak_ptr<T> _singleton;
public:
  static std::shared_ptr<T> singleton()
  {
    std::shared_ptr<T> singleton = _singleton.lock();
    if (!singleton) 
    {
      singleton.reset(new T());
      _singleton = singleton;
    }

    return singleton;
  }
};

template<typename T>
std::weak_ptr<T> Singleton<T>::_singleton;

#17楼

我们最近在EECS课堂上讨论了这个主题。 如果要详细查看讲义,请访问http://umich.edu/~eecs381/lecture/IdiomsDesPattsCreational.pdf

我知道有两种正确创建Singleton类的方法。

第一种方式:

实现它的方式类似于您在示例中的实现方式。 至于破坏,“ Singletons通常在程序运行的过程中会忍受;大多数OS会在程序终止时恢复内存和大多数其他资源,因此有一种理由不必担心这一点。”

但是,优良作法是在程序终止时进行清理。 因此,您可以使用辅助静态SingletonDestructor类来执行此操作,并将其声明为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; }
};

Singleton_destroyer将在程序启动时创建,并且“程序终止时,所有全局/静态对象都会被运行时库关闭代码(由链接器插入)破坏,因此the_destroyer将被破坏;其析构函数将删除Singleton,并运行其破坏者。”

第二路

这称为Meyers Singleton,由C ++向导Scott Meyers创建。 只需以不同的方式定义get_instance()即可。 现在,您还可以摆脱指针成员变量。

// public member function
static Singleton& Singleton::get_instance()
{
  static Singleton s;
  return s;
}

这很整洁,因为返回的值是通过引用获得的,您可以使用. 语法而不是->来访问成员变量。

“编译器自动构建代码,该代码在声明中第一次创建,而不是在之后创建,然后在程序终止时删除静态对象。”

还要注意,使用Meyers Singleton,“如果对象在终止时彼此依赖,则可能会遇到非常困难的情况-Singleton相对于其他对象何时消失?但是对于简单的应用程序,这很好。”


#18楼

我认为您应该编写一个静态函数,其中删除了您的静态对象。 当您要关闭应用程序时,应调用此函数。 这将确保您没有内存泄漏。


#19楼

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


#20楼

如何像这样使用new放置:

class singleton
{
    static singleton *s;
    static unsigned char *buffer[sizeof(singleton)/4 *4] //4 byte align
    static singleton* getinstance()
    {
        if (s == null)
        {
            s = new(buffer) singleton;
        }
        return s;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值