在头文件中声明全局常量对象
Declaring global const objects in a header file
在 Eric Niebler 的 range-v3 库中,他提供了很多头文件,每个头文件都有自己的全局函数对象。它们都以相同的方式声明。他提供了一个class模板static_const
:
template<typename T>
struct static_const
{
static constexpr T value {};
};
template<typename T>
constexpr T static_const<T>::value;
然后 F
类型的每个函数对象声明为:
namespace
{
constexpr auto&& f = static_const<F>::value;
}
通过 static_const
模板 和 在未命名的命名空间中引入对象的优点是什么,而不是只写:
static constexpr F f{};
问题基本上是一个定义规则。
如果你有:
static constexpr F f{};
名字f
有内部linkage,这意味着每个翻译单元都有自己的f
。这意味着,例如,采用 f
地址的内联函数将根据调用发生在哪个翻译单元中获得不同的地址:
inline auto address() { return &f; } // which f??
这意味着现在我们实际上可能有 address
的多个定义。真的,任何使用 f
地址的操作都是可疑的。
来自 D4381:
// <iterator>
namespace std {
// ... define __detail::__begin_fn as before...
constexpr __detail::_begin_fn {};
}
// header.h
#include <iterator>
template <class RangeLike>
void foo( RangeLike & rng ) {
auto * pbegin = &std::begin; // ODR violation here
auto it = (*pbegin)(rng);
}
// file1.cpp
#include "header.h"
void fun() {
int rgi[] = {1,2,3,4};
foo(rgi); // INSTANTIATION 1
}
// file2.cpp
#include "header.h"
int main() {
int rgi[] = {1,2,3,4};
foo(rgi); // INSTANTIATION 2
}
The code above demonstrates the potential for ODR violations if the global std::begin
function object is defined naïvely. Both functions fun in file1.cpp and main in file2.cpp cause the implicit instantiation foo<int[4]>
. Since global const objects have internal linkage, both translation units file1.cpp and file2.cpp see separate std::begin
objects, and the two foo instantiations will see different addresses for the std::begin
object. That is an ODR violation.
另一方面,有:
namespace
{
constexpr auto&& f = static_const<F>::value;
}
虽然 f
仍然具有内部 linkage,但 static_const<F>::value
具有 external linkage 因为它是静态的数据成员。当我们取 f
的地址时,它是一个引用意味着我们实际上取的是 static_const<F>::value
的地址,它在整个程序中只有一个唯一的地址。
另一种方法是使用变量模板,它需要具有外部 linkage - 这需要 C++14,并且也在相同的 link:[=36= 中进行了演示]
namespace std {
template <class T>
constexpr T __static_const{};
namespace {
constexpr auto const& begin =
__static_const<__detail::__begin_fn>;
}
}
Because of the external linkage of variable templates, every translation unit will see the same address for __static_const<__detail::__begin_fn>
. Since std::begin
is a reference to the variable template, it too will have the same address in all translation units.
The anonymous namespace is needed to keep the std::begin
reference itself from being multiply defined. So the reference has internal linkage, but the references all refer to the same object. Since every mention of std::begin
in all translation units refer to the same entity, there is no ODR violation ([basic.def.odr]/6).
在 C++17 中,有了新的内联变量特性,我们根本不用担心这个,只需写:
inline constexpr F f{};
在 Eric Niebler 的 range-v3 库中,他提供了很多头文件,每个头文件都有自己的全局函数对象。它们都以相同的方式声明。他提供了一个class模板static_const
:
template<typename T>
struct static_const
{
static constexpr T value {};
};
template<typename T>
constexpr T static_const<T>::value;
然后 F
类型的每个函数对象声明为:
namespace
{
constexpr auto&& f = static_const<F>::value;
}
通过 static_const
模板 和 在未命名的命名空间中引入对象的优点是什么,而不是只写:
static constexpr F f{};
问题基本上是一个定义规则。
如果你有:
static constexpr F f{};
名字f
有内部linkage,这意味着每个翻译单元都有自己的f
。这意味着,例如,采用 f
地址的内联函数将根据调用发生在哪个翻译单元中获得不同的地址:
inline auto address() { return &f; } // which f??
这意味着现在我们实际上可能有 address
的多个定义。真的,任何使用 f
地址的操作都是可疑的。
来自 D4381:
// <iterator> namespace std { // ... define __detail::__begin_fn as before... constexpr __detail::_begin_fn {}; } // header.h #include <iterator> template <class RangeLike> void foo( RangeLike & rng ) { auto * pbegin = &std::begin; // ODR violation here auto it = (*pbegin)(rng); } // file1.cpp #include "header.h" void fun() { int rgi[] = {1,2,3,4}; foo(rgi); // INSTANTIATION 1 } // file2.cpp #include "header.h" int main() { int rgi[] = {1,2,3,4}; foo(rgi); // INSTANTIATION 2 }
The code above demonstrates the potential for ODR violations if the global
std::begin
function object is defined naïvely. Both functions fun in file1.cpp and main in file2.cpp cause the implicit instantiationfoo<int[4]>
. Since global const objects have internal linkage, both translation units file1.cpp and file2.cpp see separatestd::begin
objects, and the two foo instantiations will see different addresses for thestd::begin
object. That is an ODR violation.
另一方面,有:
namespace
{
constexpr auto&& f = static_const<F>::value;
}
虽然 f
仍然具有内部 linkage,但 static_const<F>::value
具有 external linkage 因为它是静态的数据成员。当我们取 f
的地址时,它是一个引用意味着我们实际上取的是 static_const<F>::value
的地址,它在整个程序中只有一个唯一的地址。
另一种方法是使用变量模板,它需要具有外部 linkage - 这需要 C++14,并且也在相同的 link:[=36= 中进行了演示]
namespace std { template <class T> constexpr T __static_const{}; namespace { constexpr auto const& begin = __static_const<__detail::__begin_fn>; } }
Because of the external linkage of variable templates, every translation unit will see the same address for
__static_const<__detail::__begin_fn>
. Sincestd::begin
is a reference to the variable template, it too will have the same address in all translation units.The anonymous namespace is needed to keep the
std::begin
reference itself from being multiply defined. So the reference has internal linkage, but the references all refer to the same object. Since every mention ofstd::begin
in all translation units refer to the same entity, there is no ODR violation ([basic.def.odr]/6).
在 C++17 中,有了新的内联变量特性,我们根本不用担心这个,只需写:
inline constexpr F f{};