最近,我碰到了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的主类QApplication
在main
函数中是在堆栈上分配的,并且当没有应用程序对象可用时,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
类成为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
所需的模板参数的繁琐细节)。
#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_once
和std::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;
}
};