漂亮/施瓦茨计数器,标准兼容
Nifty/Schwarz counter, standard compliant?
今天早上我和同事讨论了静态变量初始化顺序的问题。他提到了俏丽/施瓦茨计数器,我(有点)困惑。我知道它是如何工作的,但我不确定从技术上讲,这是否符合标准。
假设以下3个文件(前两个是从More c++习语中复制过来的):
//Stream.hpp
class StreamInitializer;
class Stream {
friend class StreamInitializer;
public:
Stream () {
// Constructor must be called before use.
}
};
static class StreamInitializer {
public:
StreamInitializer ();
~StreamInitializer ();
} initializer; //Note object here in the header.
//Stream.cpp
static int nifty_counter = 0;
// The counter is initialized at load-time i.e.,
// before any of the static objects are initialized.
StreamInitializer::StreamInitializer ()
{
if (0 == nifty_counter++)
{
// Initialize Stream object's static members.
}
}
StreamInitializer::~StreamInitializer ()
{
if (0 == --nifty_counter)
{
// Clean-up.
}
}
// Program.cpp
#include "Stream.hpp" // initializer increments "nifty_counter" from 0 to 1.
// Rest of code...
int main ( int, char ** ) { ... }
…问题就在这里!有两个静态变量:
- "nifty_counter" in
Stream.cpp
;和 -
Program.cpp
中的"initializer"
由于这两个变量恰好在两个不同的编译单元中,因此没有(AFAIK) 官方保证在调用initializer
的构造函数之前将nifty_counter
初始化为0。
我可以想到两个快速的解决方案来解释为什么这个"工作":
- 现代编译器足够聪明,可以解决两个变量之间的依赖关系,并将代码按适当的顺序放在可执行文件中(可能性很小);
-
nifty_counter
实际上是在"加载时"初始化的,就像文章说的那样,它的值已经放在可执行文件的"数据段"中,所以它总是在"任何代码运行之前"初始化(极有可能)。
在我看来,这两个都依赖于一些非官方的,但可能的实现。这个标准是兼容的还是仅仅是"很可能工作",我们不应该担心它?
我相信它一定会起作用。根据标准($3.6.2/1):"具有静态存储时间(3.7.1)的对象应在任何其他初始化发生之前进行零初始化(8.5)。"
由于nifty_counter
具有静态存储持续时间,因此它在initializer
创建之前被初始化,而不考虑跨翻译单元的分布。
nifty_counter
的定义中删除初始化,所以它看起来像:
static int nifty_counter;
由于它具有静态存储持续时间,因此它将被零初始化,即使没有指定初始化式——并且删除初始化式可以消除对在零初始化之后发生的任何其他初始化的任何疑问。
我认为这个例子缺少的是如何避免Stream的构造,这通常是不可移植的。除了漂亮的计数器之外,初始化器的作用是构造如下内容:
extern Stream in;
当一个编译单元拥有与该对象相关联的内存时,在使用就地new操作符之前是否存在一些特殊的构造函数,或者在我见过的以另一种方式分配内存以避免任何冲突的情况下。在我看来,如果在这个流上有一个无操作构造函数,那么是先调用初始化函数还是不定义无操作构造函数的顺序。
要分配一个字节区域通常是不可移植的,例如对于gnu iostream, cin的空间定义为:
typedef char fake_istream[sizeof(istream)] __attribute__ ((aligned(__alignof__(istream))))
...
fake_istream cin;
llvm用途:
_ALIGNAS_TYPE (__stdinbuf<char> ) static char __cin [sizeof(__stdinbuf <char>)];
都对物体所需的空间做了一定的假设。其中Schwarz计数器初始化的位置为new:
new (&cin) istream(&buf)
实际上这看起来不那么便携。
我注意到像gnu, microsoft和AIX这样的编译器确实有编译器扩展来影响静态初始化顺序:
- 对于Gnu,这是:启用
init-priority
与-f
标志,并使用__attribute__ ((init_priority (n)))
。 - 在windows与微软编译器有一个#pragma (http://support.microsoft.com/kb/104248)
- 使用CMake检测支持的C++标准
- 如何理解C++标准N3337中的expr.const.cast子句8
- "throw expression code" 1e7 >返回 d 是什么?投掷标准::overflow_error( "too big" ) : d;意味 着?
- 编译标准库类型
- 循环在计数器中不起作用
- 标准是否使用多余的大括号(例如 T{{{10}}})定义列表初始化?
- 编译器如何在使用SFINAE的函数和标准函数之间确定两者是否可行
- 铸造标准::有没有回到原来的类型
- python集合的C++等价物是什么.计数器
- 标准 N3337 5.2.10 第 7 条中的C++"类型"是什么意思?
- this_thread::sleep_for和计时时钟之间的关系是否由C++11标准指定
- 标准库类型的赋值运算符的引用限定符
- 标准是否严格定义了该程序应该如何编译?
- Python 集合.计数器,如何避免重复查找
- 如何从Windows应用程序输出到标准?
- 安全到标准:移动会员?
- 如何正确将字符串转换为标准::时间::system_clock::time_point?
- 这是否符合C++标准:双响双响,例如!!(-0.0).
- 相同的地址,多个shared_ptr计数器,是C++标准禁止的
- 漂亮/施瓦茨计数器,标准兼容