C ++ singleton vs完全静态对象

时间:2010-10-01 16:14:48

标签: c++ class static singleton

假设我们需要在项目中只有一个类的一个实例。有几种方法可以做到。

我想比较一下。请你复习我的理解。

1)古典单身人士模式

2)完全静态的类(所有方法和成员都是静态的)。


据我所知,差异如下:

a)未定义跨不同单元的静态成员的初始化顺序。 因此,完全静态成员初始化不能使用来自其他模块的任何静态成员/函数。单身人士没有这个问题。

b)我们必须处理Singleton的getInstance()的线程。但是,完全静态类没有这个问题。

c)访问方法看起来有点不同。 FOO ::巴(); vs Foo :: getInstance() - > bar(); 通常,单例可以返回NULL以识别构造对象时存在一些问题,而静态类则不能。

d)对于静态类的一堆静态,类的定义看起来有点笨拙。

我错过了什么吗?

5 个答案:

答案 0 :(得分:20)

无论你称它为Singleton还是Monostate还是任何花哨的名字......这个令人烦恼的本质就是你有一个对象的实例和许多写入它:全局变量,无论它们是什么样的,都是邪恶

需要一个独特实例的想法通常很笨拙。大多数时候,您真正需要的是通信共享相同实例的部分。但是另一组部件可以完美地使用另一个实例而没有问题。

声称需要全局变量的任何代码都非常可疑。使用它可能看起来更简单,但让我们面对它,你可以完美地将对象传递给每个函数,它会使它们的签名变得复杂但它仍然可以工作。

但是,我承认,在您发现问题之前,使用全局变量似乎更简单:

  • 多线程受到损害
  • 可测试性会减少,因为一项测试可能会影响其后的测试
  • 依赖性分析非常复杂:当你从子方法中引入全局时,很难知道你的方法所依赖的状态......

现在,就单例而言,多线程创建在C ++ 0x之前的C ++中是不可用的(当它变得可能使用静态本地时),因此你需要在一个线程中创建它并延迟访问:instantiate它是主要的,这是你最好的选择。

破坏可能会导致混乱,因为Singleton / Static的生命可能会在其他人完成之前结束,然后是未定义的行为。这是典型的Logger单身人士。通常的策略是无耻地泄漏......

在那之后,如果你还想要一个,我祝你好运,这就是所有社区都可以为你做的。

答案 1 :(得分:10)

您忽略的另一个选项是namespace

namespace xyz {
namespace {
    int private_variable;
}

int get_pv() {
    return private_variable;
}
}

从功能上讲,这与你的选项#2类似,但你不能不小心“删除”这个东西。您不能意外地创建它的实例。它只是一组相关的全球可访问数据和功能。你可以(如我的例子中)甚至拥有“私人”成员和职能。

当然使用方式如下:

int x = xyz::get_pv();

答案 2 :(得分:1)

  

假设我们需要在项目中只有一个类的一个实例。有几种方法可以做到。

更好的解决方案:

作为参数传递给所有必需函数的main中的变量将是另一个变量。

  

a)未定义跨不同单元的静态成员的初始化顺序。因此,完全静态成员初始化不能使用来自其他模块的任何静态成员/函数。单身人士没有这个问题。

如果单身人士的构造函数/析构函数访问其他全局静态生命周期变量,则会遇到此问题。

  

b)我们必须处理Sigleton的getInstance()的线程。但是,完全静态类没有这个问题。

不是真的有问题吗?如果您知道它,只需在代码中添加适当的锁。

  

c)访问方法看起来有点不同。 FOO ::巴(); vs Foo :: getInstance() - > bar();通常,sigleton可以返回NULL以识别构造对象时存在一些问题而静态类不能。

我会让我的getInstance()返回一个引用。如果指针为NULL,则没有歧义。它要么工作,要么抛出异常。这也导致了一个设计,其中在实例上调用了正确的破坏(不要把它作为使用Singleton的建议,如果可能的话,我会避免使用它(但如果你确实使用它使它整洁)。

  

d)对于静态类的一堆静态,类的定义看起来有点笨拙。

没有比正确编写单身人士更笨拙。

这两种方法的问题在于它们都访问global mutable state,因此其他对象对这些“单实例”对象的使用对用户是隐藏的。这可能导致测试问题(TDD需要模拟外部功能,但global mutable state会阻止测试人员模拟外部依赖(轻松))。

任何非POD的对象都有一个可能会抛出异常的构造函数。因此,对于全局命名空间中的对象,这意味着在输入main()之前可以抛出异常(这可能导致很难找到错误(如果你有很多全局对象(你必须在任何地方放置断点)。)但是对于懒惰评估的单例存在同样的问题;如果在第一次使用它时会抛出如何纠正这一点,以便后续尝试不抛出?你的应用程序会在每次检索单例时继续抛出吗?

答案 3 :(得分:1)

您也可以使用Borg模式,除非您可以访问共享指针类,否则它在C ++中会稍微复杂一些。这个想法是可以实例化任意数量的Borg类,但它们的状态在所有实例中共享。

答案 4 :(得分:0)

您可以添加:静态对象可以抛出异常。可执行文件不会启动,很难调试/处理。