C++ C++;俏皮的反成语;为什么?

C++ C++;俏皮的反成语;为什么?,c++,c++11,singleton,static-initialization,C++,C++11,Singleton,Static Initialization,我最近遇到了一个问题。我的理解是,这用于在标准库(如cout、cerr等)中实现globals。由于专家选择了它,我认为这是一种非常强大的技术 我试图理解使用更像Meyer Singleton的东西有什么好处 例如,可以在头文件中包含: inline Stream& getStream() { static Stream s; return s; } static Stream& stream = getStream(); 优点是您不必担心引用计数、新的位置或有两个类,即代码更

我最近遇到了一个问题。我的理解是,这用于在标准库(如cout、cerr等)中实现globals。由于专家选择了它,我认为这是一种非常强大的技术

我试图理解使用更像Meyer Singleton的东西有什么好处

例如,可以在头文件中包含:

inline Stream& getStream() { static Stream s; return s; }
static Stream& stream = getStream();
优点是您不必担心引用计数、新的位置或有两个类,即代码更简单。既然不是这样做的,我相信这是有原因的:

  • 这是否不能保证在共享库和静态库中有一个全局对象?看起来ODR应该保证只有一个静态变量
  • 是否存在某种性能成本?在我的代码和漂亮的计数器中,您似乎都在跟随一个引用来访问对象
  • 是否存在引用计数实际有用的情况?如果包含了头文件,并且在程序结束时销毁了它,就像Meyer Singleton一样,它似乎仍然会导致构建对象
  • 答案是否包括手动打开某些东西?我在这方面没有太多经验

  • 编辑:在阅读雅克的答案时,我被提示编写以下代码,我将其作为一个快速演示添加到原始问题中。这是一个非常简单的示例,说明了使用Meyer Singleton+全局引用如何在main:.之前进行初始化。

    静态本地/Meyer的Singleton+静态全局引用(您的解决方案)几乎等同于漂亮的计数器

    区别如下:

  • 解决方案中不需要.cpp文件

  • 从技术上讲,
    静态蒸汽&
    存在于每个编译单元中;所引用的对象不存在。因为在当前版本的C++中,没有办法检测到这一点,就好像它消失了一样。但有些实现可能会创建该引用,而不是忽略它

  • 有人可以在创建
    静态流&
    之前调用
    getStream()
    ;这将导致销毁顺序困难(流的销毁时间比预期晚)。这可以通过违反规则来避免

  • 该标准要求在
    内联
    getStream
    线程中创建
    静态流
    本地。检测到这种情况不会发生对编译器来说是一个挑战,因此解决方案中可能存在一些冗余的线程安全开销。nifty计数器不明确支持线程安全;这被认为是安全的,因为它在静态初始化时运行,在预期线程之前

  • 必须在每个编译单元中调用
    getStream()
    。只有当它被证明不能做任何事情时,它才能被优化,这是很困难的。俏皮的计数器也有类似的成本,但是操作可能更简单,也可能不更容易优化运行时成本。(确定这一点需要在各种编译器上检查生成的程序集输出)

  • 在C++11中引入的“magic statics”(无竞争条件的静态局部变量)。在使用C++11 magic statics之前,您的代码可能还存在其他问题;我能想到的唯一一个方法是,在静态初始化期间,有人直接在另一个线程中调用
    getStream()
    ,这(如上所述)通常应该被禁止

  • 在标准之外,您的版本将在每个动态链接的代码块(DLL、.so等)中自动神奇地创建一个新的单例。漂亮的计数器只会在cpp文件中创建单例。这可以让库编写器更严格地控制意外生成的新单例;他们可以将其粘贴到动态库中,而不是生成副本


  • 避免一个以上的单例有时很重要。

    关于俏皮计数器(又称Schwartz计数器)的实用性/性能的所有问题基本上都由Maxim Egorushkin在回答中回答(但也请参见评论线程)

    主要问题是,双方正在进行权衡。当您使用Nifty计数器时,您的程序启动时间会慢一些(在大型项目中),因为所有这些计数器都必须在发生任何事情之前运行。这在梅尔的单身汉身上是不会发生的

    但是,在Meyer的单例中,每次您想要访问全局对象时,都必须检查它是否为null,或者,编译器发出代码,在尝试任何访问之前检查静态变量是否已构造。在这个漂亮的计数器中,您已经有了指针,您只需发射出去,因为您可以假设init发生在启动时


    因此,Nifty Counter与Meyer的singleton基本上是程序启动时间和运行时间之间的折衷。

    使用这里的解决方案,全局
    变量在静态初始化过程中的某个点被赋值,但在初始化时未指定。因此,在静态初始化期间使用来自其他编译单元的
    ,可能不起作用。Nifty计数器是一种确保全局(例如std::cout)即使在静态初始化期间也可用的方法

    #include <iostream>
    
    struct use_std_out_in_ctor
    {
        use_std_out_in_ctor()
        {
            // std::cout guaranteed to be initialized even if this
            // ctor runs during static initialization
            std::cout << "Hello world" << std::endl;
        }
    };
    
    use_std_out_in_ctor global; // causes ctor to run during static initialization
    
    int main()
    {
        std::cout << "Did it print Hello world?" << std::endl;
    }
    
    #包括
    结构使用\u标准\u输出\u输入\u
    {
    使用_std_out _in_ctor()
    {
    //std::即使是这样,也不能保证初始化
    //ctor在静态初始化期间运行
    
    std::cout总结答案和评论:

    让我们比较一个库的3个不同选项,希望将全局单例表示为变量或通过getter函数: 选项1-允许使用全局变量,即:

    • 确保一次创建
    • 作为
      // libA .h
      struct A {
          A();
      };
      
      A& getA();
      
      // some other header
      A global_a2 = getA();
      
      // main
      int main() {
          std::cerr << "main\n";
      }
      
      // libA .cpp - need to be dynamically linked! (same as libstdc++ is...)
      // thus the below shall be created only once in the process
      A& getA() {
          static A a;
          return a;
      } 
      
      A::A() { std::cerr << "construct A\n"; }