在头文件中声明全局const对象

Bar*_*rry 10 c++ global-variables c++14 range-v3

在Eric Niebler的range-v3库中,他提供了许多标题,每个标题都有自己的全局函数对象.他们都以同样的方式宣布.他提供了一个类模板static_const:

template<typename T>
struct static_const
{
    static constexpr T value {};
};

template<typename T>
constexpr T static_const<T>::value;
Run Code Online (Sandbox Code Playgroud)

然后将类型的每个函数对象F声明为:

namespace
{
    constexpr auto&& f = static_const<F>::value;
}
Run Code Online (Sandbox Code Playgroud)

通过static_const模板未命名的命名空间引入对象有什么好处,而不是只写:

static constexpr F f{};
Run Code Online (Sandbox Code Playgroud)

Bar*_*rry 2

这个问题基本上是一个定义规则。

\n\n

如果你只有:

\n\n
static constexpr F f{};\n
Run Code Online (Sandbox Code Playgroud)\n\n

该名称f具有内部链接,这意味着每个翻译单元都有自己的f. 其结果意味着,例如,采用 的地址的内联函数f将根据调用发生在哪个翻译单元中获得不同的地址:

\n\n
inline auto address() { return &f; } // which f??\n
Run Code Online (Sandbox Code Playgroud)\n\n

这意味着现在我们实际上可能有 的多个定义address。实际上,任何获取地址的操作f都是可疑的。

\n\n

来自D4381

\n\n
\n
// <iterator>\nnamespace std {\n  // ... define __detail::__begin_fn as before...\n  constexpr __detail::_begin_fn {};\n}\n\n// header.h\n#include <iterator>\ntemplate <class RangeLike>\nvoid foo( RangeLike & rng ) {\n  auto * pbegin = &std::begin; // ODR violation here\n  auto it = (*pbegin)(rng);\n}\n\n// file1.cpp\n#include "header.h"\nvoid fun() {\n  int rgi[] = {1,2,3,4};\n  foo(rgi); // INSTANTIATION 1\n}\n\n// file2.cpp\n#include "header.h"\nint main() {\n  int rgi[] = {1,2,3,4};\n  foo(rgi); // INSTANTIATION 2\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

std::begin上面的代码演示了如果全局函数对象定义为 na\xc3\xafvely,则可能会发生 ODR 违规。file1.cpp 中的 fun 函数和 file2.cpp 中的 main 函数都会导致隐式实例化foo<int[4]>。由于全局 const 对象具有内部链接,因此翻译单元 file1.cpp 和 file2.cpp 都会看到单独的std::begin对象,并且两个 foo 实例化将看到该std::begin对象的不同地址。这是 ODR 违规行为。

\n
\n\n
\n\n

另一方面,与:

\n\n
namespace\n{\n    constexpr auto&& f = static_const<F>::value;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

虽然f仍然具有内部链接,但由于它是静态数据成员而static_const<F>::value具有外部链接。当我们获取 的地址时f,它是一个引用,这意味着我们实际上正在获取 的地址static_const<F>::value,它在整个程序中只有一个唯一的地址。

\n\n

另一种方法是使用变量模板,它需要具有外部链接 - 这需要 C++14,并且也在同一个链接中进行了演示:

\n\n
\n
namespace std {\n  template <class T>\n  constexpr T __static_const{};\n  namespace {\n    constexpr auto const& begin =\n      __static_const<__detail::__begin_fn>;\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

由于变量模板的外部链接,每个翻译单元都会看到相同的地址__static_const<__detail::__begin_fn>。由于std::begin是对变量模板的引用,因此它在所有翻译单元中也将具有相同的地址。

\n\n

需要匿名命名空间来防止std::begin引用本身被多重定义。因此引用具有内部链接,但引用都引用同一个对象。由于std::begin所有翻译单元中的每次提及均指同一实体,因此不存在 ODR 违规 ( [basic.def.odr]/6 )。

\n
\n\n
\n\n

在 C++17 中,使用新的内联变量功能,我们根本不必担心这个问题,只需编写:

\n\n
inline constexpr F f{};\n
Run Code Online (Sandbox Code Playgroud)\n