提问人:Artem Barger 提问时间:6/18/2009 最后编辑:Jan SchultkeArtem Barger 更新时间:9/17/2023 访问量:954580
如何实现单例设计模式?
How do you implement the Singleton design pattern?
问:
最近,我遇到了 C++ 的单例设计模式的实现/实现。它看起来像这样(我从现实生活中的例子中采用了它):
// a lot of methods are omitted here
class Singleton
{
public:
static Singleton* getInstance( );
~Singleton( );
private:
Singleton( );
static Singleton* instance;
};
从这个声明中,我可以推断出实例字段是在堆上启动的。这意味着存在内存分配。我完全不清楚的是,内存究竟什么时候会被解除分配?还是存在错误和内存泄漏?似乎实现有问题。
我的主要问题是,我如何以正确的方式实现它?
答:
作为一个单身人士,你通常不希望它被破坏。
当程序终止时,它将被拆除并释放,这是单例的正常、期望的行为。如果你希望能够显式清理它,那么向类中添加一个静态方法相当容易,它允许你将其恢复到干净的状态,并在下次使用时重新分配它,但这超出了“经典”单例的范围。
评论
您可以避免内存分配。有许多变体,在多线程环境中都存在问题。
我更喜欢这种实现(实际上,我更喜欢这种说法并不正确,因为我尽可能避免单例):
class Singleton
{
private:
Singleton();
public:
static Singleton& instance()
{
static Singleton INSTANCE;
return INSTANCE;
}
};
它没有动态内存分配。
评论
static
static
static
INSTANCE
instance
它确实可能是从堆中分配的,但如果没有来源,就无法知道。
典型的实现(取自我在 emacs 中的一些代码)是:
Singleton * Singleton::getInstance() {
if (!instance) {
instance = new Singleton();
};
return instance;
};
...并依靠程序超出范围进行事后清理。
如果您在必须手动完成清理的平台上工作,我可能会添加手动清理例程。
这样做的另一个问题是它不是线程安全的。在多线程环境中,两个线程可以在任何一个线程有机会分配新实例之前通过“if”(因此两者都会)。如果您无论如何都依靠程序终止来清理,这仍然没什么大不了的。
评论
另一个非分配的替代方案:创建一个单例,比如类,根据需要:C
singleton<C>()
用
template <class X>
X& singleton()
{
static X x;
return x;
}
在当前的 C++ 中,这和 Cătălin 的答案都不是自动线程安全的,而是在 C++0x 中。
评论
在 2008 年,我提供了一个 C++ 98 的 Singleton 设计模式实现,它是延迟评估的、保证销毁的、非技术线程安全的:
任何人都可以为我提供 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 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
};
请参阅这篇关于何时使用单例的文章:(不经常)
单例:应该如何使用它
请参阅这两篇关于初始化顺序以及如何应对的文章:
静态变量初始化顺序 查找 C++ 静态初始化顺序问题
请参阅这篇描述生存期的文章:
C++ 函数中静态变量的生存期是多少?
请参阅这篇文章,其中讨论了对单例的一些线程影响:
声明为 GetInstance 方法的静态变量的单例实例,它是线程安全的吗?
请参阅这篇文章,它解释了为什么双重检查锁定在 C++ 上不起作用:
C++ 程序员应该了解的所有常见未定义行为是什么?
Dobbs博士:C++和双重检查锁定的危险:第一部分
评论
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
What irks me most though is the run-time check of the hidden boolean in getInstance()
这是对实现技术的假设。不需要假设它是活着的。请参阅 stackoverflow.com/a/335746/14065 您可以强制一种情况,使其始终处于活动状态(开销小于 )。全局变量在初始化顺序(跨编译单元)方面存在更多问题,因为您不强制顺序。这种模型的优点是 1) 延迟初始化。2)执行命令的能力(施瓦茨有帮助,但更丑陋)。是的,要丑得多。Schwarz counter
get_instance()
已接受答案中的解决方案有一个明显的缺点 - 单例的析构函数在控件离开函数后被调用。当在内部分配一些依赖对象时,确实可能会出现问题。main()
main
当我尝试在Qt应用程序中引入Singleton时,我遇到了这个问题。我决定,我所有的设置对话框都必须是单例对话框,并采用了上面的模式。不幸的是,Qt的主类被分配在函数的堆栈上,Qt禁止在没有应用程序对象可用时创建/销毁对话框。QApplication
main
这就是为什么我更喜欢堆分配的单例。我为所有单例提供了一个显式和方法,并在内部调用它们。因此,我可以完全控制单例创建/销毁的顺序,并且我保证无论是否有人调用,都会创建单例。init()
term()
main
getInstance()
评论
这是关于对象生存期管理的。假设您的软件中不止有单例。它们依赖于 Logger 单例。在应用程序销毁期间,假设另一个单例对象使用 Logger 来记录其销毁步骤。您必须保证最后清理 Logger。因此,也请查看这篇论文: http://www.cs.wustl.edu/~schmidt/PDF/ObjMan.pdf
上面链接的论文描述了双重检查锁定的缺点,即编译器可能会在调用对象的构造函数之前为对象分配内存,并设置指向已分配内存地址的指针。但是,在 c++ 中,使用分配器手动分配内存,然后使用构造调用来初始化内存非常容易。使用此应用程序,双重检查锁定工作正常。
评论
如果要在堆中分配对象,为什么不使用唯一指针。内存也将被释放,因为我们使用的是唯一的指针。
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);
评论
m_s
static
getInstance()
m_s.get()
nullptr
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 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 是单例对象的内存位置,在程序的持续时间内相同,但(通常)每次运行程序时都不同。
注意这不是一个线程安全的线程。您必须确保线程安全。
除了此处的其他讨论之外,可能值得注意的是,您可以具有全局性,而不限制使用一个实例。例如,考虑引用计数的情况......
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
friend
Store
如果它本身是一个模板化类,那么事情会变得更混乱,但仍然可以使用这种方法,也许可以通过实现具有以下签名的帮助程序类: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>. */ };
用户现在可以为每个全局实例创建一个类型(和全局实例),并始终通过其包装实例访问存储(从而忘记了使用所需的模板参数的血腥细节)。StoreWrapper
Store
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() {}
};
评论
= delete
C++
= delete
std::shared_ptr
我在答案中没有找到 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;
}
};
要使用 just inherit your class from this,例如:class Test : public Singleton<Test>
评论
有没有人提到过?
大多数其他方法(包括双重检查锁定)都已损坏。std::call_once
std::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。这些笔记(以及我在这个答案中给出的引文)是由我的教授大卫·基拉斯(David Kieras)创作的。
我知道有两种方法可以正确创建 Singleton 类。
第一种方式:
实现它的方式类似于示例中的实现方式。至于破坏,“单例通常会忍受程序运行的长度;当程序终止时,大多数操作系统都会恢复内存和大多数其他资源,因此有理由不担心这一点。
但是,最好在程序终止时进行清理。因此,您可以使用辅助静态 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; }
};
// somewhere in code (Singleton.cpp is probably the best place)
// create a global static Singleton_destroyer object
Singleton_destoyer the_destroyer;
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 何时相对于其他对象消失?但对于简单的应用程序,这很好用。
评论
您的代码是正确的,只是您没有在类外部声明实例指针。静态变量的内部类声明在 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
要知道,我们不需要手动删除单例实例。在整个程序中,我们需要它的单个对象,因此在程序执行结束时,它将自动解除分配。
以下是我对如何做适当的单例(和其他重要的静态对象)的看法: https://github.com/alex4747-pub/proper_singleton
总结:
- 使用静态初始化列表在正确的时间实例化单例:输入 main 之后和启用多线程之前
- 添加一些小的改进,使其对单元测试友好。
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 的可模拟单例。它依赖于一个小帮助程序在任何时候(最多)强制执行单个对象。要强制执行单个对象,请删除重置(我们发现这对测试很有用)。
A 可以这样实现:ConcreteSinleton
class ConcreteSingleton : public Singleton<ConcreteSingleton>
{
public:
ConcreteSingleton(const Singleton<ConcreteSingleton>::PrivatePass&)
: Singleton<StandardPaths>::Singleton{pass}
{}
// ... concrete interface
int 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 和 NotMovable clases 允许您更具体地定义单例(有时您希望移动单个实例)。
它将类的实例化限制为一个对象。当只需要一个对象来协调整个系统的操作时,这很有用
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();
}
评论