【问题标题】:Best way of defining a compile-time constant定义编译时常量的最佳方法
【发布时间】:2015-02-27 00:25:15
【问题描述】:

在 C++11 中定义简单常量值的最佳方法是什么,这样就不会产生运行时损失?例如:(无效代码)

// Not ideal, no type, easy to put in wrong spot and get weird errors
#define VALUE 123

// Ok, but integers only, and is it int, long, uint64_t or what?
enum {
     Value = 123
};

// Could be perfect, but does the variable take up memory at runtime?
constexpr unsigned int Value = 123;

class MyClass {
    // What about a constant that is only used within a class, so
    // as not to pollute the parent namespace?  Does this take up
    // memory every time the class is instantiated?  Does 'static'
    // change anything?
    constexpr unsigned int Value = 123;

    // What about a non-integer constant?
    constexpr const char* purpose = "example";
    std::string x;
    std::string metadata() { return this->x + (";purpose=" purpose); }
    // Here, the compiled code should only have one string
    // ";purpose=example" in it, and "example" on its own should not
    // be needed.
};

编辑

因为有人告诉我这是一个无用的问题,因为它背后没有背景,这就是背景。

我正在定义一些标志,以便我可以做这样的事情:

if (value & Opaque) { /* do something */ }

Opaque 的值在运行时不会改变,所以它只在编译时才需要,让它出现在我的编译代码中似乎很愚蠢。这些值也在一个循环中使用,该循环对图像中的每个像素运行多次,所以我想避免运行时查找会减慢它(例如,在运行时检索常量值的内存访问。)这不是t 过早优化,因为该算法目前处理一张图像大约需要一秒钟,而且我通常有超过 100 张图像要处理,所以我希望它尽可能快。

既然人们说这是微不足道的,不用担心,我猜#define 是尽可能接近字面值,所以也许这是避免“过度思考”问题的最佳选择?我想普遍的共识是你只是希望没有人需要使用Opaque这个词或你想使用的其他常量?

【问题讨论】:

  • 您确定您的内存如此紧张以至于 4 个字节会有所不同吗?
  • 为什么大家总是这么说?然后你发布你想要的东西,你得到的只是风滚草,因为没有人能弄清楚你想要什么,除了一个人说你应该发布一个只有核心点的简单例子......叹息
  • @Brian:当然 4 个字节并不多,但是您是否会在代码周围留下未使用的变量?为什么要删除它们,可以禁用编译器警告,并且几个字节并不多:-P 当然,一个原因是,如果您在循环多次的算法中使用这些值,那么直接在代码中删除一个值每次访问时查找内存,使您的算法更快(尽管有编译器优化。)
  • 是的,您应该发布一个仅包含核心点的简单示例,但这些核心点应该与现实有所关联。这个问题并不表示任何实际问题。它只是在没有实际定义任何约束的情况下征求意见,使其毫无用处!
  • 好吧,我使用字段作为标志,例如if (value & SomeFlag) { /* do something */ }。与其说if (pixel & 2),不如说if (value & Opaque),因为它更容易阅读,但Opaque 的值在运行时不会改变。仅在编译时才需要它,并且仅仅因为我希望我的代码易于阅读而将其嵌入到我的可执行文件中似乎很愚蠢。它也在一个为每个像素运行多次的循环内,所以我想避免运行时查找会减慢它的速度。所以我追求最优雅的常量定义方式,重点关注新的 C++11/14 特性。

标签: c++ c++11 constants constexpr idioms


【解决方案1】:

确实,这比看起来要复杂。

只是为了明确重申要求:

  1. 不应有运行时计算。
  2. 除了实际结果外,不应有静态、堆栈或堆内存分配。 (不可能禁止分配可执行代码,但请确保 CPU 所需的任何数据存储都是私有的。)

在 C++ 中,表达式可以是左值或纯右值(在 C++11 之前和 C 中,右值对应于相关概念)。左值指的是对象,因此它们可以出现在赋值表达式的L左手侧。对象存储和左值是我们想要避免的。

您想要的是一个标识符,或 id-expression,以评估为纯右值。

目前,只有枚举器可以做到这一点,但正如您所观察到的,它们还有一些不足之处。每个枚举声明都引入了一个新的、不同的类型,因此enum { Value = 123 }; 引入了一个不是整数的常量,而是它自己的唯一类型,转换int。这不是适合这项工作的工具,尽管它可以在紧要关头工作。

您可以使用#define,但这是一个技巧,因为它完全避免了解析器。您必须用全部大写字母命名它,然后确保相同的全大写名称不用于程序中的其他任何内容。对于库接口,这样的保证尤其繁重。

下一个最佳选择是函数调用:

constexpr int value() { return 123; }

不过要小心,因为constexpr 函数仍然可以在运行时进行评估。您需要再跳一圈才能将此值表示为计算:

constexpr int value() {
    /* Computations that do not initialize constexpr variables
       (or otherwise appear in a constant expression context)
       are not guaranteed to happen at compile time, even
       inside a constexpr function. */

    /* It's OK to initialize a local variable because the
       storage is only temporary. There's no more overhead
       here than simply writing the number or using #define. */

    constexpr int ret = 120 + 3;
    return ret;
}

现在,您不能将常量称为名称,它必须是value()。函数调用运算符可能看起来效率较低,但它是当前唯一完全消除存储开销的方法。

【讨论】:

  • 你是说简单的return 123 + 3;不会在编译时执行?
  • @vsoftco 不完全是。但是constexpr 变量总是保证被视为常量表达式,而constexpr 函数则不是,并且有时可以在运行时进行评估。当然,对于这样一个微不足道的操作,它并没有什么不同。但如果是int ret = INT_MAX + 3;,那就不同了,因为会有有符号整数溢出。非常宽松地使用constexpr 有助于防止此类问题。
  • @Potatoswatter - 整数溢出是 UB。编译器不需要在运行时执行。
  • @Wormer 123 本身就是一个表达式,它有我们想要的纯右值类别。问题是将其绑定到可重用名称foo,以便foo123 具有相同的类别。
  • @Wormer 是的,那里的内容并不重要。我试图在示例中插入一个计算,但这有点令人困惑。
【解决方案2】:

我认为您应该考虑C++11 feature of specifying an underlying type for an enum,应用于您的示例将是:

enum : unsigned int { Value = 123 };

这消除了您对使用枚举的两个反对意见之一,即您无法控制实际使用哪种类型来表示它们。不过,它仍然不允许使用非整数常量。

【讨论】:

    【解决方案3】:

    你不会出错的:

    static constexpr const unsigned int Value = 123;
    

    不过,老实说,尽量不要在意这些。喜欢,真的试试。

    【讨论】:

    • 你可能会大错特错。 ODR-在内联函数(例如模板)中使用这样的常量将违反单一定义规则,因为名称指的是每个翻译单元中单独的不同对象。
    • 当C++有53910种定义常量的方式时,很难不关心它。
    • @LightnessRacesinOrbit 如果你是强迫症的完美主义者,那就不是这样了。它困扰着我。
    • @LightnessRacesinOrbit 如果你有数百个常量并且你正在编写一个只有几千字节程序空间的嵌入式系统,那么它就会成为一个问题。说 C++ 不用于此类嵌入式编程是循环推理:只要我们注意极端情况,C++ 就能发挥最大的效率。
    • @LightnessRacesinOrbit 因此,当问题以抽象的方式陈述时,您要求一个用例,当您获得一个用例时,您要求谈论抽象机器。 ???
    猜你喜欢
    • 2015-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-05
    • 2023-03-07
    • 2020-04-22
    • 2012-02-19
    • 1970-01-01
    相关资源
    最近更新 更多