用C ++ 1z定义全局常量?

时间:2017-08-16 10:06:35

标签: c++ constants c++17

C ++ 1z 中声明内存效率全局常量的最佳方法是什么? {{3> } ,所以在所有 internal linkage 中使用了一个副本?

尽管在许多地方已经提到过,但我们并没有任何单一的"最好的方法"问题和答案,所以在这里。以下是我找到相关问题的部分地方列表。

我正在寻找至少所有基本基本类型的解决方案,例如intdoublecharstd::string

编辑1: 通过最好的方式,我的意思是"内存效率高" 。我知道对于内部联系,每个翻译单元都会有多个副本,因此会占用内存。除此之外,如果我们能够实现快速的执行时间和编译时间,这将是伟大的,代码的美化(容易)并不重要。此外,我知道外部链接将增加获取时间,因为处理器可能会在运行时获得缓存未命中。但最新的C ++语言是否支持所有这些的最佳方法?或者可能建议支持每个案例?

注意:对于std::string全局常量,最佳方法是什么?是否可变性会对 constness 产生影响吗?

3 个答案:

答案 0 :(得分:5)

回到过去,我们在头文件中声明一个常量并在源文件中定义它:

// constants.h
extern const int size;

// constants.cpp
#include "constants.h"
const int size = 3;

// usage
std::cout << size << '\n';

但也许这太简单了;为什么不用10行模板和时髦的实例化语法来支持它呢?

答案 1 :(得分:4)

摘要

在C ++ 17中,定义全局常量或全局变量的最简单方法通常是inline variable。只需在头文件中添加如下所示的行即可创建一个:

inline const std::string greeting = "Hello!";

如果全局常量为literal type,则更喜欢使用inline constexprinline是可选的,如果它是静态类成员)而不是inline const

inline constexpr std::string_view greeting = "Hello!"sv;

这也适用于变量,但许多优点不再适用,因此您可能希望使用其他方法:

inline unsigned int score = 0;

详细

首先,此方法的两个主要 dis 优势是:

  1. 它在C ++ 17之前不起作用,因此如果您需要C ++ 14兼容性或更早版本,则需要执行其他操作。 template trick VTT suggests与旧标准兼容,并且应该具有与内联变量类似的语义,但如果您只需要C ++ 17支持,则不再需要它。
  2. 编译比extern variables慢一些,因为编译器需要合并多个定义,因为优化器可以使用更多信息。如果你可能改变定义而不是数据类型,那么“有些”部分会变成“明显”的部分;由于该值现在位于头文件中,这意味着重新编译包含头的所有内容,而不仅仅是重新链接。如果您可能需要更改数据类型,无论如何都需要重新编译。
  3. 如果这些对你来说都不重要,我认为这种方法胜过了用external linkage得到一个全局常量的其他方法,并且在最终的可执行文件中最多只有一个定义。

    这样的内联变量只在一个文件中提到,因此很容易改变;这对于仅头文件库特别有用。这也意味着它在编译时可以使用它的值,因此优化器可以看到它并可能消除一些用法。

    使用constexpr

    在C ++ 17中,静态constexpr类成员自动inline,所以如果你的全局常量应该是类范围的一部分,你可以做类似的事情

    constexpr int favorite_number = -3;
    

    否则,您需要说constexpr inline,这应该仍然有用。这将具有上述语义,但也具有constexpr的通常优点,因此编译器将知道它可以尝试在编译时执行更多操作。例如:

    #include <string_view>
    
    using namespace std::literals;
    
    inline constexpr std::string_view greeting = "Hello!"sv;
    
    inline constexpr int scrabble_points[greeting.size()] = {4, 1, 1, 1, 1, 0};
    
    int main() {
      int total = 0;
      for (int i : scrabble_points) {
        total += i;
      }
      return total;
    }
    

    可以使用constexpr,但不能只使用inline,因为constexpr知道greeting.size()是编译时常量,可以用作大小一个数组。通过优化,这个could compile to a just a single mov instruction and ret,不包括字符串或数组的任何副本,因为它是不必要的。

    使用新的内联语义,main之前的所有内容都可能位于包含在多个位置的头文件中,并且最多只能有一个副本。

    变量

    同样的方法通过不使用const

    轻松支持可变变量
    inline std::string player_name = "<subject name here>";
    

    这是一个带有外部链接的全局变量。由于它是一个变量,我在Pete’s answer上提到的大多数优点都消失了,但有些(比如只在一个地方声明变量而不需要将任何额外的东西链接起来)仍然存在。但是,它们可能不值得花费额外的编译时间和缺乏C ++ 14兼容性。

    ¹对于constconstexpr变量,如果不需要,编译器/优化器可能会完全消除该变量。从理论上讲,可能决定将其复制到直接值或其他东西;在实践中,您可能不应该担心这一点,因为只有在有充分理由的情况下才会这样做,这应该使最终的可执行文件更小和/或更快。您可以使用-Os代替-O3来调整此内容。

    ²使用该常量的每个目标文件仍然有一个副本,但这些副本将在链接时合并。避免这种情况的唯一方法是使用extern变量。

    ³这个简化的示例即使没有inline也可以使用,或者数组只有const而不是constexpr,但这些对于更复杂的实际情况非常有用。

答案 2 :(得分:1)

我建议使用模板包装器,它允许在头文件中定义任何类型的全局常量,并且只会在所有翻译单元中产生一个副本(不需要C ++ 1z):

template<typename TDummy = void> class
t_GlobalValueHolder final
{
     public: using
     t_Value = const int; // can be anything

     public: static t_Value s_value;
};

template<typename TDummy>
typename t_GlobalValueHolder<TDummy>::t_Value
t_GlobalValueHolder<TDummy>::s_value{42};

// usage
::std::cout << t_GlobalValueHolder<>::s_value;

注意:通常,s_value的访问权限应仅限于类/方法的子集,方法是保护值并从t_GlobalValueHolder派生用户类,或者仅将所有相关项列为朋友t_GlobalValueHolder。适当的访问控制是这种方法相对于“extern const”的另一个好处,除了不需要有一个包含值定义的翻译单元。