如果这样编译,将明显违反c++的单一定义规则:
// Case 1
// something.h
struct S {};
struct A
{
static const S val = S();
};
,因为如果某物。h包含在多个模块中,则会重复A::val
的定义。但是,这是允许的:
// Case 2
// someOtherThing.h
struct B
{
static const int val = 3;
};
在我的理解中,这种情况是ok的原因是因为B::val
是整型的编译时常数,所以编译器本质上可以用文字3
搜索并替换对B::val
的所有引用(并且检查反汇编显示这正是它所做的)。因此,在某种意义上,最终产品中没有 0 B::val
定义,因此ODR不适用。但是,考虑一下:
// Case 3
// yetAnotherThing.h
struct C
{
static const int val = 3;
const int* F()
{
return &val;
}
};
这是允许的,并且反汇编表明,在这种情况下,实际上已经留出了一些内存位置来存储C::val
的值。从表面上看,这意味着如果yetAnotherThing.h包含在多个模块中,我们现在违反了ODR,因为static const int val = 3
现在导致"释放"存储。然而编译器和链接器(vc++ 2012)都没有报错。
为什么?这只是编译器/链接器的作者必须处理的一个令人讨厌的特殊情况吗?如果是这样,为什么不能用同样的系统来解决第一个问题呢?
(欢迎引用标准中的相关内容,但它们不一定能回答问题。如果标准说任何使用pink_elephants
关键字都应该导致数字42的每个实例被666替换,那么就是这样,但是我们仍然想知道为什么存在这样一个奇怪的规则。
您的第一个示例不是违反ODR,因为在类中声明静态成员则不是一个定义,只是一个声明。静态成员必须是在单个翻译单元中定义,例如:
S const A::val;
在源文件中(不是头文件)。
在c++ 11之前版本中,当声明为静态时,具有整型如果是const,则允许(作为特殊例外)指定一个初始化项,前提是该初始化项是常量积分表达式。然而,即使在这种情况下,你也正式地只需要一个定义(不带初始化式)一个源文件。如果没有,结果是不确定的的行为。(IIFC,但有一个例外:如果变量为only用于需要整型常量表达式的上下文中,不需要定义。)
我认为c++ 11已经扩展了一些,并且允许了一些非整型也可以在类中具有初始化式定义。但是它仍然需要外部的定义类。
关于你的最后一个例子,你声称是有效的:它是在c++ 11之前版本中是不合法的,并且在许多情况下会导致错误编译器。(我的印象是c++ 11做到了合法,让编译器生成实例,但我一时找不到合适的词。如果这是真的在c++ 11中是合法的,那么这只是c++ 11的一个特性,而vc++ 2012实现。)