在 Google C++ Style Guide 中,命名空间部分指出“在头文件中使用未命名的命名空间很容易导致违反 C++ 单一定义规则 (ODR)。 ”
我明白为什么不在实现文件中使用未命名的命名空间会导致 ODR 违规,但不知道在标头中使用如何做到这一点。这怎么会导致违规?
在 Google C++ Style Guide 中,命名空间部分指出“在头文件中使用未命名的命名空间很容易导致违反 C++ 单一定义规则 (ODR)。 ”
我明白为什么不在实现文件中使用未命名的命名空间会导致 ODR 违规,但不知道在标头中使用如何做到这一点。这怎么会导致违规?
原因是,如果您实际使用匿名命名空间中的任何内容,您将面临未定义行为的风险。例如:
namespace {
double const pi = 3.14159;
}
inline double twoPiR( double r ) { return 2.0 * pi * r; }
内联函数(和类、模板以及必须在多个翻译单元中定义的任何其他内容)的规则是标记必须相同(通常情况下,除非您点击某个宏),并且所有符号必须相同绑定. 在这种情况下,每个翻译单元都有一个单独的实例pi
,因此pi
intwoPiR
绑定到每个翻译单元中的不同实体。(有一些例外,但它们都涉及整数表达式。)
当然,即使没有匿名命名空间,这也是未定义的行为(因为const
默认情况下意味着内部链接),但基本原则是成立的。任何在未命名命名空间(或在标头中定义的任何 const 对象)的标头中的任何使用都可能导致未定义的行为。这是否是一个真正的问题取决于,但肯定任何真正涉及上述地址的东西pi
都会引起问题。(我在这里说“真的”,因为有很多情况下,地址或引用是正式使用的,但在实践中,内联扩展会导致实际使用的值。当然,令牌不管3.14159
它3.14159
在哪里出现。)
在 test.h
namespace {
int x;
}
namespace{
int x;
}
在任何源文件中包含该头文件都会违反 ODR,因为x
它被定义了两次。发生这种情况是因为编译器为未命名的命名空间提供了唯一标识符,并且翻译单元中所有出现的未命名命名空间都被赋予了相同的标识符。套用一句话:每个 TU 最多有一个未命名的命名空间。