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)
这个问题基本上是一个定义规则。
\n\n如果你只有:
\n\nstatic constexpr F f{};\nRun Code Online (Sandbox Code Playgroud)\n\n该名称f具有内部链接,这意味着每个翻译单元都有自己的f. 其结果意味着,例如,采用 的地址的内联函数f将根据调用发生在哪个翻译单元中获得不同的地址:
inline auto address() { return &f; } // which f??\nRun Code Online (Sandbox Code Playgroud)\n\n这意味着现在我们实际上可能有 的多个定义address。实际上,任何获取地址的操作f都是可疑的。
来自D4381:
\n\n\n\n\nRun Code Online (Sandbox Code Playgroud)\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\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\nnamespace\n{\n constexpr auto&& f = static_const<F>::value;\n}\nRun Code Online (Sandbox Code Playgroud)\n\n虽然f仍然具有内部链接,但由于它是静态数据成员而static_const<F>::value具有外部链接。当我们获取 的地址时f,它是一个引用,这意味着我们实际上正在获取 的地址static_const<F>::value,它在整个程序中只有一个唯一的地址。
另一种方法是使用变量模板,它需要具有外部链接 - 这需要 C++14,并且也在同一个链接中进行了演示:
\n\n\n\n\nRun Code Online (Sandbox Code Playgroud)\n\nnamespace 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由于变量模板的外部链接,每个翻译单元都会看到相同的地址
\n\n__static_const<__detail::__begin_fn>。由于std::begin是对变量模板的引用,因此它在所有翻译单元中也将具有相同的地址。需要匿名命名空间来防止
\nstd::begin引用本身被多重定义。因此引用具有内部链接,但引用都引用同一个对象。由于std::begin所有翻译单元中的每次提及均指同一实体,因此不存在 ODR 违规 ( [basic.def.odr]/6 )。
在 C++17 中,使用新的内联变量功能,我们根本不必担心这个问题,只需编写:
\n\ninline constexpr F f{};\nRun Code Online (Sandbox Code Playgroud)\n