1

如果它编译,这样的事情将明显违反 C++ 单一定义规则:

// Case 1
// something.h

struct S {};

struct A
{
   static const S val = S();
};

因为如果 something.h 包含在多个模块中,A::val则会重复定义。但是,这是允许的:

// Case 2    
// someOtherThing.h

struct B
{
   static const int val = 3;
};

据我了解,这种情况可以的原因是因为B::val它是一个整数类型的编译时常量,所以编译器基本上可以B::val用文字对所有引用进行搜索和替换3(反汇编的检查表明了这一点正是它的作用)。因此,在某种意义上,最终产品中的定义为零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)都没有抱怨。

为什么?这只是编译器/链接器的作者必须处理的令人讨厌的特殊情况吗?如果是这样,为什么不能使用相同的系统来使案例 #1 也可以工作?

(欢迎引用标准中的相关引用,但它们不一定能回答问题。如果标准说pink_elephants关键字的任何使用都应该导致数字 42 的每个实例都被替换为 666,那么就是这样,但我们'仍然会想知道为什么存在这样一个奇怪的规则。)

4

1 回答 1

3

您的第一个示例违反 ODR,因为在类中声明静态成员不是定义,而只是声明。静态成员必须在单个翻译单元中定义,例如:

S const A::val;

在源文件(不是标题)中。

在 C++11 之前的版本中,当声明为静态、整数类型且为 const 时,允许(作为特殊例外)指定初始化程序,前提是初始化程序是常量整数表达式。然而,即使在这种情况下,您也正式需要将定义(没有初始化程序)放在一个源文件中,并且只需要一个源文件。如果它丢失,结果是未定义的行为。(IIFC,有一个例外:如果变量仅用于需要整数常量表达式的上下文中,则不需要定义。)

我认为 C++11 对此进行了一些扩展,并允许一些非整数类型在类定义中具有初始化器。但它仍然需要类之外的定义。

关于您声称有效的最后一个示例:它在 C++11 之前的版本中是 合法的,并且会在许多编译器中导致错误。(我的印象是 C++11 使其合法,并将其留给编译器生成实例,但我无法立即找到合适的词。如果它在 C++11 中合法, 那么这只是 VC++2012 实现的 C++11 特性。)

于 2014-05-27T14:54:15.033 回答