C++单例设计模式

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

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

从这个声明中,我可以推断出实例字段是在堆上启动的。这意味着有内存分配。我完全不清楚的是内存到底什么时候会被释放?或者是否有bug和内存泄漏?似乎是实现有问题。

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

898480 次浏览

作为一个Singleton,你通常不希望它被破坏。

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

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

我更喜欢这种实现(实际上,说我更喜欢是不正确的,因为我尽可能避免单例):

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

它没有动态存储分配。

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

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

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

…并依靠超出范围的程序来清理。

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

这样做的另一个问题是它不是线程安全的。在多线程环境中,两个线程可能会在任何一个有机会分配新实例之前通过“if”(所以两者都会)。如果你依赖程序终止来清理,这仍然不是什么大不了的事情。

另一个非分配替代方案:根据需要创建一个单例,比如classC

singleton<C>()

使用

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

在当前C++中,这和科特林的答案都不是自动线程安全的,而是C++0x。

2008年,我提供了一个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 inaccessible(especially from outside), otherwise, you may accidentally get copies of// your singleton appearing.S(S const&);              // Don't Implementvoid 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};

查看这篇关于何时使用单例的文章:(不经常)
单例:应该如何使用

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

查看这篇描述生命周期的文章:
C++函数中静态变量的生命周期是多少?

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

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

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

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

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

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

上面链接到的那篇论文描述了双重检查锁定的缺点,即编译器可以在调用对象的构造函数之前为对象分配内存,并设置一个指向分配内存地址的指针。然而,c++很容易使用分配器手动分配内存,然后使用构造调用初始化内存。使用这个appraoch,双重检查锁定工作得很好。

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

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 Implementvoid operator=(S const&); // Don't implement};
std::unique_ptr<S> S::m_s(0);
#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);

这是一个简单的实现。

#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 destructorSingletonClass() { cout << "SingletonClass instance created!\n"; }~SingletonClass() {}
// private copy constructor and assignment operatorSingletonClass(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!00915CB800915CB8

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

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

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

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

引用者不需要将指针存储回各自的Store,因为该信息是在编译时提供的。你也不必担心Store的生命周期,因为编译器要求它是全局的。如果确实只有一个Store的实例,那么这种方法没有开销;使用多个实例,编译器在代码生成方面是否聪明取决于。如有必要,ItemRef类甚至可以成为Storefriend(你可以有模板化的朋友!)。

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

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

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

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

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

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

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

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

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

我的实现与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;

我们最近在我的EECS课上讨论了这个话题。如果你想详细查看课堂笔记,请访问http://umich.edu/~eecs381/lecture/IdiomsDesPattsCreational.pdf。这些笔记(以及我在这个答案中给出的引用)是由我的教授大卫·基拉斯创作的。

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

第一种方式:

至于破坏,“单例通常会持续程序运行的时间;当程序终止时,大多数操作系统会恢复内存和大多数其他资源,所以有理由不担心这个问题。”

但是,在程序终止时清理是一种很好的做法。因此,您可以使用辅助的静态SingletonDestructor类来执行此操作,并将其声明为Singleton中的朋友。

class Singleton {public:static Singleton* get_instance();  
// disable copy/move -- this is a SingletonSingleton(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 Singletonclass Singleton_destroyer {public:~Singleton_destroyer { delete Singleton::ptr; }};
// somewhere in code (Singleton.cpp is probably the best place)// create a global static Singleton_destroyer objectSingleton_destoyer the_destroyer;

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

第二条路

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

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

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

msgstr"编译器自动构建第一次通过声明,之后不声明,然后删除程序中的静态对象结束。”

还请注意,使用Meyers Singleton,如果对象在执行任务时相互依赖,您“可能会陷入非常困难的情况终止-当Singleton相对于其他对象消失时?但对于简单的应用程序,这很好。”

您的代码是正确的,除了您没有在类之外声明实例指针。静态变量的内部类声明在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实例不需要我们手动删除。我们在整个程序中需要它的单个对象,因此在程序执行结束时,它将被自动解除分配。

这是我对如何做正确的单例(和其他重要的静态对象)的看法:https://github.com/alex4747-pub/proper_singleton

总结:

  1. 使用静态初始化列表在正确的时间实例化单例:在进入main之后和启用多线程之前
  2. 添加一些小的改进,使其对单元测试友好。

C++11线程安全实现:

 #include <iostream>#include <thread>

class Singleton{private:static Singleton * _instance;static std::mutex mutex_;
protected:Singleton(const std::string value): value_(value){}~Singleton() {}std::string value_;
public:/*** Singletons should not be cloneable.*/Singleton(Singleton &other) = delete;/*** Singletons should not be assignable.*/void operator=(const Singleton &) = delete;
//static Singleton *GetInstance(const std::string& value);static Singleton *GetInstance(const std::string& value){if (_instance == nullptr){std::lock_guard<std::mutex> lock(mutex_);if (_instance == nullptr){_instance = new Singleton(value);}}return _instance;}
std::string value() const{return value_;}};
/*** Static methods should be defined outside the class.*/Singleton* Singleton::_instance = nullptr;std::mutex Singleton::mutex_;

void ThreadFoo(){std::this_thread::sleep_for(std::chrono::milliseconds(10));Singleton* singleton = Singleton::GetInstance("FOO");std::cout << singleton->value() << "\n";}
void ThreadBar(){std::this_thread::sleep_for(std::chrono::milliseconds(1000));Singleton* singleton = Singleton::GetInstance("BAR");std::cout << singleton->value() << "\n";}
int main(){std::cout <<"If you see the same value, then singleton was reused (yay!\n" <<"If you see different values, then 2 singletons were created (booo!!)\n\n" <<"RESULT:\n";std::thread t1(ThreadFoo);std::thread t2(ThreadBar);t1.join();t2.join();std::cout << "Complete!" << std::endl;
return 0;}

这是一个使用CRTP可模拟单例。它依赖于一个小帮手在任何时候(最多)强制执行单个对象。要在程序执行上强制执行单个对象,请删除重置(我们发现这对测试很有用)。

ConcreteSinleton可以这样实现:

class ConcreteSingleton : public Singleton<ConcreteSingleton>{public:ConcreteSingleton(const Singleton<ConcreteSingleton>::PrivatePass&): Singleton<StandardPaths>::Singleton{pass}{}  
// ... concrete interfaceint f() const {return 42;}
};

然后使用与

ConcreteSingleton::instance().f();

我想在这里展示C++中单例的另一个例子。使用模板编程是有意义的。此外,从不可复制且不可移动的类派生单例类也是有意义的。这是代码中的样子:

#include<iostream>#include<string>
class DoNotCopy{protected:DoNotCopy(void) = default;DoNotCopy(const DoNotCopy&) = delete;DoNotCopy& operator=(const DoNotCopy&) = delete;};
class DoNotMove{protected:DoNotMove(void) = default;DoNotMove(DoNotMove&&) = delete;DoNotMove& operator=(DoNotMove&&) = delete;};
class DoNotCopyMove : public DoNotCopy,public DoNotMove{protected:DoNotCopyMove(void) = default;};
template<class T>class Singleton : public DoNotCopyMove{public:static T& Instance(void){static T instance;return instance;}
protected:Singleton(void) = default;};
class Logger final: public Singleton<Logger>{public:void log(const std::string& str) { std::cout << str << std::endl; }};


int main(){Logger::Instance().log("xx");}

拆分成NotCopyable和NotMoable类允许您更具体地定义您的单例(有时您想移动您的单例实例)。

它将类的实例化限制为一个对象。当只需要一个对象来协调整个系统的操作时,这很有用

class Singleton {private:int data;static Singleton* instance;Singleton();public:static Singleton* getInstance();};Singleton* Singleton::instance = 0;Singleton::Singleton(){this->data = 0;cout << "constructor called.." << endl;}
 

Singleton* Singleton::getInstance() {if (!instance) {instance = new Singleton();return instance;}}int main() {Singleton *s = s->getInstance();Singleton *s1 =s1->getInstance();}