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

问题描述 投票:9回答:1

在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;

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

namespace
{
    constexpr auto&& f = static_const<F>::value;
}

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

static constexpr F f{};
c++ global-variables c++14 range-v3
1个回答
0
投票

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

如果你有:

static constexpr F f{};

f这个名字有内部联系,这意味着每个翻译单位都有自己的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
}

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


另一方面,有:

namespace
{
    constexpr auto&& f = static_const<F>::value;
}

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

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

namespace std {
  template <class T>
  constexpr T __static_const{};
  namespace {
    constexpr auto const& begin =
      __static_const<__detail::__begin_fn>;
  }
}

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

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


在C ++ 17中,我们不必为新的内联变量功能而烦恼,只需编写:

inline constexpr F f{};
© www.soinside.com 2019 - 2024. All rights reserved.