【问题标题】:C++ Best practices for constants常量的 C++ 最佳实践
【发布时间】:2012-03-27 19:26:13
【问题描述】:

我有一大堆常量,我想在代码的不同部分访问它们,但我希望能够轻松访问它们:

static const bool doX = true;
static const bool doY = false;
static const int maxNumX = 5;

等等。

所以我创建了一个名为“constants.h”的文件,并将它们全部粘贴在那里,并将其#included 到任何需要知道常量的文件中。

问题是,这对于编译时间来说很糟糕,因为每次我更改一个常量时,都必须重建 constants.h 引用的所有文件。 (另外,据我了解,因为它们是静态的,所以每次我在新的 .cpp 中包含 constants.h 时,我都会在代码中生成一份 doX/doY/maxNumX 的副本,从而导致编译后的千字节浪费空间EXE——有什么办法可以看到吗?)。

所以,我想要一个解决方案。如果可能的话,不是“仅在使用它们的文件中声明常量”。

有什么建议吗?

【问题讨论】:

  • 首先:哦,不,几KB!其次:编译器​​几乎肯定会将它们折叠成一个常量,即使它没有从最终的可执行文件中完全删除。
  • 你为什么认为有物理副本?除非需要物理位置(例如获取变量的地址),否则编译器可以很高兴地优化它。
  • 如果你想让它们折叠起来,编译器需要在编译时知道每个常量的值
  • 你能想象作为一名 Microsoft 程序员,因为 Windows.h 的某些变化而不得不花一天时间重新编译吗?

标签: c++ constants


【解决方案1】:

唯一的选择是让你的常量extern 并在另一个.cpp 文件中定义它们,但是你将失去优化的潜力,因为编译器在编译每个 .cpp 时不知道它们有什么值` .

顺便说一句,不要担心大小增加:对于整数类型,您的常量可能会直接内联到生成的机器代码中。

最后,static 不是必需的,因为默认情况下,const 全局变量在 C++ 中是 static

【讨论】:

  • 链接时间优化可以轻松解决这个问题。
  • 一个整数类型的 extern const 变量不再是编译时表达式,因此它不能再用于数组边界,在 case 语句等中。跨度>
【解决方案2】:

我认为你的基本假设不成立。

您的其他标题通常是通过将协同工作的内容放在一起来组织的。例如,一个类及其相关方法或两个高度相互关联的类。

为什么将所有常量组合在一个标题中?它没有任何意义。像 "global.h" 标头那样轻松地包含每个依赖项是一个糟糕的主意。

通常,常量用于特定的上下文。例如,用作特定函数标志的枚举:

class File {
public:
  enum class Mode {
    Read,
    Write,
    Append
  };

  File(std::string const& filename, Mode mode);

  // ...
};

在这种情况下,这些常量与它们绑定到的类(甚至在类中)位于同一个标头中是很自然的。

另一类常量是那些渗透到整个应用程序的常量。例如:

enum class Direction {
  Up,
  Down,
  Right,
  Left,
  Forward,
  Backward
};

...在一个游戏中,您想要表达物体所面对方向的移动。

在这种情况下,为这组特定的常量创建一个头文件就可以了。

如果您真的担心将这些文件组合在一起:

constants/
  Direction.hpp
  Sandwich.hpp
  State.hpp

当您添加一个常量时,您将巧妙地回避重新编译整个应用程序的问题......但如果您需要这样做,您只需支付一次成本,这比您错误的设计要好'余下的工作都得靠你过活。

【讨论】:

  • +1 当添加/更改常量导致几乎所有其他源文件都需要重新编译时,我将这种现象称为“重新编译世界”。其他人可能已经发明了这种表达方式。
【解决方案3】:

您在标头中将它们声明为extern,并在实现文件中定义它们。

这样,当你想改变它们的值时,你修改了实现文件,不需要完全重新编译。

您的变体中的问题与编译无关,而是与逻辑有关。它们不会是全局变量,因为每个翻译单元都有自己的变量副本。

编辑:

C++ 风格的做法实际上是将它们包装在一个类中:

//constants.h
class Constants
{
public:
   static const bool doX;
   static const bool doY;
   static const int maxNumX;
}

//constants.cpp
const bool Constants::doX = true;
const bool Constants::doY = false;
const int Constants::maxNumX = 5;

【讨论】:

  • 如果这样做,它们将不是常量表达式,这是有问题的。
  • 我会说这是 Java-ish 方法。
  • 我会说他们 C++ 的方式是将它们包装在命名空间而不是类中。
  • @DeadMG:我原以为因为它们是 const POD,所以它们仍然是 const 表达式。
  • @LuchianGrigore 尝试使用extern const 变量作为界限来声明一个数组。
【解决方案4】:

这种用法有什么问题?
不要在头文件中声明 static 类型,它不会做你认为它做的事情。

当您在头文件中声明一个静态变量时,会在包含该头文件的每个 Translation Unit(TU) 中创建该变量的副本,因此每个 TU 会看到一个不同的变量,这与您的相反期望有一个全球性的。

建议的解决方案:
您应该在头文件中将它们声明为extern,并将它们定义在一个cpp文件中,同时在要访问它们的每个cpp文件中包含带有extern的头。

好读:
How should i use extern?

【讨论】:

  • 在这种情况下(因为它们是 const),我实际上会使用静态(我的意思是我不会声明它们任何东西,只是不让它们成为外部(因此默认为静态))。因为它为编译器提供了更好的机会来完全优化该值。
  • const 在全局和命名空间范围级别意味着内部链接,即static constconst 相同,但不是extern const。此行为是在 C++ 中专门实现的,以避免将宏用于常量。如果不采用该常量的地址,编译器不会将该常量放入目标文件的 .data 部分。它的值将被硬编码,因为它本质上是一个编译时间常数。
【解决方案5】:

另一种最适合编译时间(但运行时间成本较低)的方法是通过类中的静态方法访问常量。

//constants.h
class Constants
{
public:
  static bool doX();
  static bool doY();
  static int maxNumX();
};

//constants.cpp
bool Constants::doX() { return true; }
bool Constants::doY() { return false; }
int Constants::maxNumX() { return 42; }

这种方法的优点是,如果您在标头中添加/删除/更改方法的声明,您只需重新编译所有内容,而更改任何方法返回的值只需要编译 constants.cpp(当然还有链接)。

与大多数事情一样,这可能是最好的,也可能不是最好的,这是您的特殊情况,但它是另一个需要考虑的选择。

【讨论】:

    【解决方案6】:

    直接的方法是,创建非常量符号:

    const bool doX = true;
    const bool doY = false;
    const int maxNumX = 5;
    

    这些值将被编译器替换为给定的值。那是最有效的方法。一旦您修改或添加值,这当然也会导致重新编译。但在大多数情况下,这不会引起实际问题。

    当然有不同的解决方案:

    • 使用 static consts(或静态 const 类成员)可以修改值,而无需重新编译所有引用的文件 - 但因此这些值保存在将在运行时调用的 const 数据段中,而不是在编译时解决。如果运行时性能没有问题(对于 90% 的大多数典型代码来说都是如此),那没关系。

    • 直接的 C++ 方式是使用类 enums 而不是全局 const 标识符(如我的 Mathieu 所述)。这更安全,除此之外,它的工作原理与const 一样:符号将在编译时解析。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-01-21
      • 2010-10-04
      • 2011-03-06
      • 2013-08-03
      • 1970-01-01
      • 2017-11-30
      • 1970-01-01
      相关资源
      最近更新 更多