最近,我遇到了 C++ 单例设计模式的实现/实现。它看起来像这样(我从现实生活的例子中采用了它):
// a lot of methods are omitted here
class Singleton
{
public:
static Singleton* getInstance( );
~Singleton( );
private:
Singleton( );
static Singleton* instance;
};
从这个声明中,我可以推断出实例字段是在堆上启动的。这意味着有内存分配。对我来说完全不清楚的是何时释放内存?还是有错误和内存泄漏?执行起来好像有问题。
我的主要问题是,如何以正确的方式实施它?
2008 年,我提供了单例设计模式的 C++98 实现,它是惰性求值、保证破坏、非技术线程安全的:
Can any one provide me a sample of Singleton in c++?
这是单例设计模式的更新 C++11 实现,它是惰性求值、正确销毁和 thread-safe。
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
};
请参阅这篇关于何时使用单例的文章:(不经常)
Singleton: How should it be used
请参阅这两篇关于初始化顺序以及如何应对的文章:
Static variables initialisation order
Finding C++ static initialization order problems
请参阅这篇描述生命周期的文章:
What is the lifetime of a static variable in a C++ function?
请参阅这篇讨论对单例的一些线程影响的文章:
Singleton instance declared as static variable of GetInstance method, is it thread-safe?
请参阅这篇解释为什么双重检查锁定在 C++ 上不起作用的文章:
What are all the common undefined behaviours that a C++ programmer should know about?
Dr Dobbs: C++ and The Perils of Double-Checked Locking: Part I
您可以避免内存分配。有很多变体,在多线程环境中都有问题。
我更喜欢这种实现方式(实际上,我更喜欢这种说法并不正确,因为我尽可能避免使用单例):
class Singleton
{
private:
Singleton();
public:
static Singleton& instance()
{
static Singleton INSTANCE;
return INSTANCE;
}
};
它没有动态内存分配。
static
实例,但从链接的角度来看,它们都没有问题。 static
的第一次出现是关于静态成员函数的,它与链接无关。 static
的第二次出现是关于 INSTANCE
的存储时长。该对象将在程序运行期间一直存在于内存中,您不能在 TU 之外通过名称访问它并不重要,因为您可以通过具有外部链接的成员函数 instance
访问它。
作为一个单例,你通常不希望它被破坏。
当程序终止时,它将被拆除并释放,这是单例的正常行为。如果您希望能够显式清理它,可以很容易地向该类添加一个静态方法,该方法允许您将其恢复到干净状态,并在下次使用时重新分配它,但这超出了 a 的范围“经典”单身人士。
但是,有时对于多个静态对象,您需要能够保证在使用单例的所有静态对象不再需要它之前,不会破坏单例。
在这种情况下,std::shared_ptr
可用于为所有用户保持 singleton 活动,即使在程序结束时调用静态析构函数:
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# 程序员,这个语法对我来说有点奇怪。或者您能否提供一个链接,我可以在其中阅读有关此确切语法的信息?
C++
将自动生成函数来复制对象。如果您想防止您的对象被复制,您可以“删除”这些功能。所以 = delete
告诉编译器不要生成它们。
另一种非分配替代方案:根据需要创建一个单例,例如 C
类:
singleton<C>()
使用
template <class X>
X& singleton()
{
static X x;
return x;
}
这和 Cătălin 的答案在当前 C++ 中都不是自动线程安全的,但会在 C++0x 中。
我没有在答案中找到 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>
我们最近在我的 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;
}
这很简洁,因为返回的值是引用的,您可以使用 .
语法而不是 ->
来访问成员变量。
“编译器会自动构建第一次通过声明创建's'的代码,而不是此后,然后在程序终止时删除静态对象。”
还要注意,使用 Meyers Singleton,“如果对象在终止时相互依赖,您可能会遇到非常困难的情况——Singleton 相对于其他对象什么时候消失?但对于简单的应用程序,这很好用。”
这是一个简单的实现。
#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 是单例对象的内存位置,在程序运行期间相同,但(通常!)每次程序运行时都不同。
注意这不是线程安全的。你必须确保线程安全。
已接受答案中的解决方案有一个明显的缺点 - 在控件离开 main()
函数后调用单例的析构函数。当在 main
中分配一些依赖对象时,确实可能存在问题。
我在尝试在 Qt 应用程序中引入 Singleton 时遇到了这个问题。我决定,我所有的设置对话框都必须是单例,并采用了上面的模式。不幸的是,Qt 的主类 QApplication
是在 main
函数的堆栈上分配的,当没有可用的应用程序对象时,Qt 禁止创建/销毁对话框。
这就是为什么我更喜欢堆分配的单例。我为所有单例提供了明确的 init()
和 term()
方法并在 main
中调用它们。因此,我可以完全控制单例创建/销毁的顺序,并且我保证无论是否有人调用 getInstance()
,都会创建单例。
有人提到 std::call_once
和 std::once_flag
吗?大多数其他方法——包括双重检查锁定——都被破坏了。
单例模式实现中的一个主要问题是安全初始化。唯一安全的方法是使用同步屏障保护初始化序列。但这些障碍本身需要安全启动。 std::once_flag
是确保安全初始化的机制。
如果要在堆中分配对象,为什么不使用唯一指针。由于我们使用的是唯一指针,因此内存也将被释放。
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
设为 getInstance()
的本地 static
并立即初始化它而无需测试。
m_s.get()
和 nullptr
会比比较 0
更好。
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;
}
它确实可能是从堆中分配的,但是没有来源就无法知道。
典型的实现(取自我已经在 emacs 中的一些代码)是:
Singleton * Singleton::getInstance() {
if (!instance) {
instance = new Singleton();
};
return instance;
};
...然后依靠超出范围的程序进行清理。
如果您在必须手动完成清理的平台上工作,我可能会添加手动清理例程。
这样做的另一个问题是它不是线程安全的。在多线程环境中,两个线程可以在任何一个有机会分配新实例之前通过“if”(所以两者都会)。如果您仍然依赖程序终止来清理,这仍然不是什么大不了的事。
除了此处的其他讨论之外,可能值得注意的是,您可以拥有全局性,而无需将使用限制在一个实例中。例如,考虑引用计数的情况......
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);
refs 不需要将指针存储回它们各自的 Store
,因为该信息是在编译时提供的。您也不必担心 Store
的生命周期,因为编译器要求它是全局的。如果确实只有一个 Store
实例,那么这种方法没有开销;对于不止一个实例,编译器在代码生成方面是否聪明。如有必要,甚至可以将 ItemRef
类设为 Store
的 friend
(您可以有模板化的朋友!)。
如果 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
所需的模板参数的血腥细节) .
这是使用 CRTP 的 mockable singleton。它依赖 a little helper 在任何时候(最多)强制执行单个对象。要在程序执行上强制执行单个对象,请删除重置(我们发现这对测试很有用)。
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();
这是关于对象生命周期管理的。假设您的软件中有多个单例。他们依赖于 Logger 单例。在应用程序销毁期间,假设另一个单例对象使用 Logger 记录其销毁步骤。你必须保证 Logger 应该最后被清理。因此,还请查看这篇论文:http://www.cs.wustl.edu/~schmidt/PDF/ObjMan.pdf
我的实现类似于 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;
您的代码是正确的,只是您没有在类之外声明实例指针。静态变量的内部类声明在 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
概括:
使用静态初始化列表在正确的时间实例化单例:在进入 main 之后和启用多线程之前添加小的改进以使其对单元测试友好。
我想在这里展示另一个 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 类允许您更具体地定义单例(有时您想要移动单个实例)。
它将类的实例化限制为一个对象。当需要一个对象来协调整个系统的动作时,这很有用
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();
}
上面链接的论文描述了双重检查锁定的缺点是编译器可能会在调用对象的构造函数之前为对象分配内存并设置指向分配内存地址的指针。在 c++ 中很容易使用分配器手动分配内存,然后使用构造调用来初始化内存。使用这种方法,双重检查锁定工作得很好。
简单的单例类,这一定是你的头类文件
#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);
#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);
不定期副业成功案例分享
What irks me most though is the run-time check of the hidden boolean in getInstance()
这是对实现技术的假设。不需要假设它还活着。请参阅 stackoverflow.com/a/335746/14065 您可以强制一种情况使其始终处于活动状态(开销低于Schwarz counter
)。全局变量在初始化顺序(跨编译单元)方面存在更多问题,因为您不强制执行顺序。该模型的优点是 1) 延迟初始化。 2)执行命令的能力(施瓦茨有帮助,但更丑陋)。是的get_instance()
更丑陋。