【问题标题】:Static objects and singletons静态对象和单例
【发布时间】:2010-12-17 15:42:12
【问题描述】:

我正在使用 boost 单例(来自序列化)。

例如,有些类继承了boost::serialization::singleton。他们每个人在它的定义附近都有这样的定义(在h文件中):

#define appManager ApplicationManager::get_const_instance()
class ApplicationManager: public boost::serialization::singleton<ApplicationManager> { ... };

每次更新(近 17 毫秒)时,我都必须调用该类的某个方法,例如 200 次。所以代码是这样的:

for (int i=0; i < 200; ++i)
   appManager.get_some_var();

我用gprof 查看函数调用堆栈,发现boost::get_const_instance 每次都调用。也许,在发布模式下编译器会优化这个?

我的想法是制作一些全局变量,例如:

ApplicationManager &handle = ApplicationManager::get_const_instance();

并使用handle,这样就不会每次都调用get_const_instnace。对吗?

【问题讨论】:

  • 最简单的解决方案是根本不使用单例。
  • @deadmg 我知道有人会发布这个 :) 请不要说使用单身人士,无论如何他们都会。
  • 有人总是发布它,因为它是真的。
  • 删除#define 并使用内联函数,拜托哦拜托。

标签: c++ function boost singleton call


【解决方案1】:

不用Singleton anti-pattern,只用一个全局变量就可以了。更诚实。

Singleton 的主要好处是当您想要延迟初始化,或者比全局变量允许您更细粒度地控制初始化顺序时。看起来这些事情都不是您关心的问题,所以只需使用全局变量。

就个人而言,我认为具有全局变量或单例的设计几乎肯定会被破坏。但是对于每个 h(is/er) 自己。

如果您一心想使用单例,您提出的性能问题很有趣,但可能不是问题,因为函数调用开销可能小于 100ns。正如所指出的,您应该配置文件。如果它真的很关心您,请在循环之前存储对 Singleton 的本地引用:

ApplicationManager &myAppManager = appManager;
for (int i=0; i < 200; ++i)
   myAppManager.get_some_var();

顺便说一句,以这种方式使用 #define 是一个严重的错误。几乎所有将预处理器用于基于编译时标志的条件编译以外的任何事情的情况都可能是一个不好的用途。 Boost 确实广泛使用了预处理器,但主要是为了绕过 C++ 的限制。不要模仿它。

最后,该函数可能正在做一些重要的事情。单例的get_instance 方法的工作之一是避免多个线程同时初始化同一个单例。对于全局变量,这应该不是问题,因为它们应该在您启动任何线程之前进行初始化。

【讨论】:

  • 我完全同意。我刚刚编写了一个单例来修补损坏的设计。
【解决方案2】:

真的有问题吗?我的意思是,您的应用程序真的会因为这种行为而受到影响吗?

我会鄙视这样的解决方案,因为从所有效果来看,您都在反对单例模式的好处之一,即避免使用全局变量。如果你想使用全局变量,那就不要使用 Singleton 了吧?

【讨论】:

  • 是的,我正在编写 3d 应用程序(游戏)。这只是一个例子。但是速度是非常非常重要的事情。
  • 那么,也许编写我自己的单例类并直接访问实例会更好?
  • 你应该分析它。无论如何,如果您使用 inline 访问器编写自己的单例类,您应该获得更好的性能。
  • @Ockonal - 函数调用开销可能在 100 纳秒左右。我怀疑这是个问题。
【解决方案3】:

是的,这当然是一个可能的解决方案。我不完全确定 boost 在幕后对其单身人士做了什么。您可以在代码中自己查找。

在大多数方面,单例模式就像创建一个全局对象并访问该全局对象。有一些区别:

1) 单例对象实例在第一次访问之前不会创建,而全局对象是在程序启动时创建的。 2) 因为单例对象是在第一次访问时才创建的,所以它实际上是在程序运行时创建的。因此,当构造函数实际运行时,单例实例可以访问程序中其他完全构造的对象。 3) 因为你是通过 getInstance() 方法(boost 的 get_const_instance 方法)访问单例的,所以执行那个方法调用会有一点开销。

因此,如果您不关心单例的实际创建时间,并且可以忍受在程序启动时创建它,您可以使用全局变量并访问它。如果你真的需要程序启动后创建的单例,那么你需要单例。在这种情况下,您可以抓住并保持对 get_const_instance() 返回的对象的引用并使用该引用。

虽然你应该知道,但过去让我感到痛苦的事情。您实际上获得了对单例拥有的对象的引用。您不拥有该对象。

1) 不要编写会导致析构函数执行的代码(例如,在返回的引用上使用共享指针),或编写任何其他可能导致对象最终处于错误状态的代码。

2) 在多线程应用程序中,如果对象可能被多个线程使用,请注意正确锁定对象中的字段。

3) 在多线程应用程序中,确保所有持有对象引用的线程在程序卸载之前终止。我见过一个案例,单例的代码驻留在一个 DLL 库中。保存引用的线程位于另一个 DLL 库中。当程序结束时,线程仍然处于活动状态。保存单例代码的 DLL 首先被卸载;还活着的线程试图对单例对象做一些事情并导致崩溃。

【讨论】:

    【解决方案4】:

    在您想要控制对进程或应用程序范围内某些内容的访问级别超出全局变量可以以更优雅的方式实现的情况的情况下,单例具有它们的优势。

    然而,库中提供的大多数单例对象将被设计为确保某种程度的线程安全,并且最有可能通过互斥锁或其他可能影响性能的关键部分锁定对实例的访问。

    在游戏或 3D 应用程序中,性能是关键,如果线程安全不是问题并获得一些性能,您可能需要考虑制作自己的轻量级单例。

    【讨论】:

      猜你喜欢
      • 2015-06-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-19
      • 1970-01-01
      • 1970-01-01
      • 2011-05-20
      相关资源
      最近更新 更多