C++中如何使用内联说明符来保留一个定义规则?

How is the inline specifier used in C++ to preserve the one definition rule?

我一直在努力弄清楚 inline 说明符如何保留 ODR。到目前为止,我写的所有内容似乎都没有必要,因为包含守卫确保定义只包含一次。

假设我在名为 constants.h

的文件中有以下定义
#ifndef CONSTANTS_H
#define CONSTANTS_H

namespace constants {
    inline const double pi { 3.14159255358979323846 };
    inline const double e  { 2.71828182845904523536 };
}

#endif

根据我对与 ODR 相关的 inline 的理解,编写 inline 说明符是为了确保这些常量的定义仅在多个翻译单元中初始化一次。因此,如果我将此文件包含在 a.cppb.cpp 中,一切都应该很好。

现在,让我们删除 inline 关键字。

#ifndef CONSTANTS_H
#define CONSTANTS_H

namespace constants {
    const double pi { 3.14159255358979323846 };
    const double e  { 2.71828182845904523536 };
}

#endif

现在,如果我将其包含在 a.cppb.cpp 中,则没有问题。我想这是因为 include guards 确保同一事物的多个定义不会出现两次。

接下来,让我们移除包含防护

namespace constants {
    const double pi { 3.14159255358979323846 };
    const double e  { 2.71828182845904523536 };
}

还是没问题。可能是因为 const 限定的变量定义默认具有内部链接。因此,在 a.cppb.cpp 中包含 constants.h 使得这些定义中的每一个在默认情况下都在其各自的翻译单元中。

很难跨多个翻译单元打破 ODR。现在让我们删除常量。

namespace constants {
    double pi { 3.14159255358979323846 };
    double e  { 2.71828182845904523536 };
}

现在! ODR 在多个翻译单元中被打破。让我们尝试用 inline 来解决这个问题,这样编译器就知道只定义这些变量一次。

namespace constants {
    inline double pi { 3.14159255358979323846 };
    inline double e  { 2.71828182845904523536 };
}

好的,没有更多的错误,这个文件可以再次包含在多个翻译单元中。那么为什么将头文件中的常量声明为 inline 被认为是“最佳实践”呢?打破 ODR 似乎需要付出很多努力,而且 inline 在存在 include 守卫的情况下是多余的。

未使用说明符 extern 声明的常量具有内部链接。

所有包含这些声明的编译单元

namespace constants {
    const double pi { 3.14159255358979323846 };
    const double e  { 2.71828182845904523536 };
}

有自己的常数pi和e。

来自 C++ 14 标准(3.5 程序和链接)

3 A name having namespace scope (3.3.6) has internal linkage if it is the name of

(3.2) — a variable of non-volatile const-qualified type that is neither explicitly declared extern nor previously declared to have external linkage; or

与上述声明相反这些声明

namespace constants {
    double pi { 3.14159255358979323846 };
    double e  { 2.71828182845904523536 };
}

有外部链接。因此,如果这些声明(也是定义)包含在多个编译单元中,编译器会发出错误,因为一个定义规则被破坏。

如果您在未命名的命名空间中声明它们,则可以使上述变量具有内部链接,例如

namespace constants {
    namespace {
        double pi { 3.14159255358979323846 };
        double e  { 2.71828182845904523536 };
    }
}

至于这些声明

namespace constants {
    inline double pi { 3.14159255358979323846 };
    inline double e  { 2.71828182845904523536 };
}

那么可以在多个编译单元中定义具有外部链接的内联变量。此外,应在使用 ODR 的每个编译单元中定义一个内联变量。