C++ 单例:这个解决方案有多好? Advantages/disadvantages, 备选方案
C++ Singletons: how good is this solution? Advantages/disadvantages, alternatives
我正在开发一个具有多个 class 必须是单例的 C++ 项目,它们之间存在依赖关系(初始化顺序很重要)。
我想到了这个解决方案:
- 我想成为单例的所有 classes 都有 protected constructors,例如:
class MySingleton1
{
protected:
MySingleton1();
}
- 有一个源文件 singleton_factory.cpp 包含实例化的 class Singletons 派生来自所有我想成为单身人士的 classes,像这样:
#include "MySingleton1.hpp"
#include "MySingleton2.hpp"
class Singletons : public MySingleton1, public MySingleton2 {}
static Singletons s_singletons;
- 仍然在singleton_factory.cpp中,对于每个单例类型,还实现了getSingleton的特化 函数:
template<>
MySingleton1& getSingleton()
{
return s_singletons;
}
template<>
MySingleton2& getSingleton()
{
return s_singletons;
}
- getSingleton 的特化将在 singleton_factory.hpp 中的通用模板化变体下 "hid":
template <class TSingleton>
TSingleton& getSingleton();
优点:
Low-coupling:
- Singleton classes不需要是"aware"的Singletons class,只需要将它们的构造函数隐藏在下面受保护的限定符(这甚至不是强制性的,只是良好做法)。唯一真正知道 Singletons class 的代码是 singleton_factory.cpp
- 具体实例的瘦依赖:想要使用类型 T 的单例的代码 只需要包含类型 Theader和瘦子singleton_factory.hpp
初始化顺序可以通过改变单例的继承顺序来控制class
- 无惰性初始化 => thread-safe?
- getSingleton()快,没有dynamic_cast,没有reinterpret_cast
缺点:
- 每次出现新的单例类型时,getSingleton 专业化,做同样的事情 - 即“return s_singletons;" 必须添加到 singleton_factory.cpp
所以,据我所知,这实际上相当不错所以我想就这样保留它,但我问征求您的反馈意见(还有什么比编程社区更好的地方呢?)。
您从这个解决方案中看到了什么额外的advantages/disadvantages?
您有什么备选方案?
这会强制单例集中化,这可能会扰乱更复杂项目中的依赖关系。具有 singleton.cpp
的库必须依赖于 每个单例所需的一切 。同时,任何使用单例的人都必须依赖singleton.cpp
库。
基本上你的代码只能在一个单一的非模块化项目中工作。将其扩展到多个动态库几乎是不可能的。
您的初始化顺序必须手动维护。
静态全局变量的构造点与 main
中第一个表达式之前的所有内容都是无序的。
我使用过的一个不错的解决方案是创建一个包含单例内存的动态库。
要成为单例,您需要继承 CRTP 助手,它提供 ::Instance()
内联方法。想要单例的人使用 ::Instance()
.
::Instance()
创建静态局部变量生存期令牌。然后它会尝试从主 DLL 中获取单例的存储空间;如果对象已经创建,它只是将存储转换为对象类型,并增加其引用计数。
如果没有,它会创建新的存储并在其中构造对象。
在销毁静态局部变量生存期令牌时,它减少了引用计数。如果该引用计数达到 0,它会在当前动态库中本地销毁它。
单例的生命周期现在是 ::Instance()
创建的变量生命周期的并集。销毁发生在非类型擦除代码中,因此我们不必担心代码被卸载的 DLL。存储是核心。存储存储的DLL必须低于Singleton系统的每个用户,但它又没有依赖性,所以这并不痛苦。
这远非完美;单例和生命周期是一个持续存在的问题,因为干净的程序关闭是困难的,并且由于单例的存在而变得更加困难。但到目前为止,它已经在一个相当大的项目中发挥了作用。
你可以在你的案例中使用依赖注入吗?即让一些序列化代码创建每个 class 的实例并将对实例的引用传递给需要访问它们的任何其他实例的构造函数?如果适用,这可能会简化您的设计。理想情况下,您可以将一个单独的、新创建的实例传递给每个构造函数以进一步减少耦合,但似乎您需要共享状态。无论如何,也许它有帮助。
我正在开发一个具有多个 class 必须是单例的 C++ 项目,它们之间存在依赖关系(初始化顺序很重要)。
我想到了这个解决方案:
- 我想成为单例的所有 classes 都有 protected constructors,例如:
class MySingleton1 { protected: MySingleton1(); }
- 有一个源文件 singleton_factory.cpp 包含实例化的 class Singletons 派生来自所有我想成为单身人士的 classes,像这样:
#include "MySingleton1.hpp" #include "MySingleton2.hpp" class Singletons : public MySingleton1, public MySingleton2 {} static Singletons s_singletons;
- 仍然在singleton_factory.cpp中,对于每个单例类型,还实现了getSingleton的特化 函数:
template<> MySingleton1& getSingleton() { return s_singletons; } template<> MySingleton2& getSingleton() { return s_singletons; }
- getSingleton 的特化将在 singleton_factory.hpp 中的通用模板化变体下 "hid":
template <class TSingleton> TSingleton& getSingleton();
优点:
Low-coupling:
- Singleton classes不需要是"aware"的Singletons class,只需要将它们的构造函数隐藏在下面受保护的限定符(这甚至不是强制性的,只是良好做法)。唯一真正知道 Singletons class 的代码是 singleton_factory.cpp
- 具体实例的瘦依赖:想要使用类型 T 的单例的代码 只需要包含类型 Theader和瘦子singleton_factory.hpp
初始化顺序可以通过改变单例的继承顺序来控制class
- 无惰性初始化 => thread-safe?
- getSingleton()快,没有dynamic_cast,没有reinterpret_cast
缺点:
- 每次出现新的单例类型时,getSingleton 专业化,做同样的事情 - 即“return s_singletons;" 必须添加到 singleton_factory.cpp
所以,据我所知,这实际上相当不错所以我想就这样保留它,但我问征求您的反馈意见(还有什么比编程社区更好的地方呢?)。
您从这个解决方案中看到了什么额外的advantages/disadvantages?
您有什么备选方案?
这会强制单例集中化,这可能会扰乱更复杂项目中的依赖关系。具有 singleton.cpp
的库必须依赖于 每个单例所需的一切 。同时,任何使用单例的人都必须依赖singleton.cpp
库。
基本上你的代码只能在一个单一的非模块化项目中工作。将其扩展到多个动态库几乎是不可能的。
您的初始化顺序必须手动维护。
静态全局变量的构造点与 main
中第一个表达式之前的所有内容都是无序的。
我使用过的一个不错的解决方案是创建一个包含单例内存的动态库。
要成为单例,您需要继承 CRTP 助手,它提供 ::Instance()
内联方法。想要单例的人使用 ::Instance()
.
::Instance()
创建静态局部变量生存期令牌。然后它会尝试从主 DLL 中获取单例的存储空间;如果对象已经创建,它只是将存储转换为对象类型,并增加其引用计数。
如果没有,它会创建新的存储并在其中构造对象。
在销毁静态局部变量生存期令牌时,它减少了引用计数。如果该引用计数达到 0,它会在当前动态库中本地销毁它。
单例的生命周期现在是 ::Instance()
创建的变量生命周期的并集。销毁发生在非类型擦除代码中,因此我们不必担心代码被卸载的 DLL。存储是核心。存储存储的DLL必须低于Singleton系统的每个用户,但它又没有依赖性,所以这并不痛苦。
这远非完美;单例和生命周期是一个持续存在的问题,因为干净的程序关闭是困难的,并且由于单例的存在而变得更加困难。但到目前为止,它已经在一个相当大的项目中发挥了作用。
你可以在你的案例中使用依赖注入吗?即让一些序列化代码创建每个 class 的实例并将对实例的引用传递给需要访问它们的任何其他实例的构造函数?如果适用,这可能会简化您的设计。理想情况下,您可以将一个单独的、新创建的实例传递给每个构造函数以进一步减少耦合,但似乎您需要共享状态。无论如何,也许它有帮助。