定义编译时常量的最佳方式

19

如何在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的值在运行时不会改变,因此它仅在编译时需要使用,让它出现在编译后的代码中似乎很愚蠢。这些值还在循环内部使用,而该循环对图像中的每个像素运行多次,因此我想避免运行时查找,以避免减慢速度(例如,运行时进行内存访问以检索常量的值)。这不是过早优化,因为该算法目前需要约一秒钟来处理一个图像,而我经常需要处理100多个图像,因此我希望速度尽可能快。

由于有人说这很琐碎,不必担心,我猜#define可能是最接近字面值的选项,因此也许这是避免“过度思考”问题的最佳选择?我想普遍共识是您只能希望没有人需要使用单词Opaque或其他要使用的常量?


你确定你的内存如此紧张,4个字节会有所不同吗? - Brian Bi
1
为什么每个人都会这样说呢?你发帖想要什么,但却只得到了荒漠中的风滚草,因为没有人能够理解你的需求,除了一个人建议你发布一个简单的例子,只包含核心要点...唉。 - Malvineous
4
@Brian: 当然,四个字节并不多,但你会在代码中留下未使用的变量吗?何必删除它们,编译器警告可以被禁用,而且几个字节并不重要:-P当然,一个原因是如果您在循环算法中使用这些值,那么直接将值放入代码中可以消除每次访问时的一个内存查找,从而加快算法的速度(尽管编译器优化除外)。 - Malvineous
是的,您应该发布一个简单的示例,只包括核心要点,但这些核心要点应该与现实有所关联。这个问题并没有说明任何实际的问题。它只是在询问某些事情的意见,而没有真正定义任何限制条件,使其毫无用处! - Lightness Races in Orbit
жҲ‘зҡ„е®һи·өиҝҳжІЎжңүжӣҙж–°еҲ°C++11пјҢдҪҶжҲ‘еҸӘжҳҜеңЁе‘ҪеҗҚз©әй—ҙдёӯдҪҝ用常规зҡ„const int a = 0;пјҢжңүж—¶еңЁзұ»дёӯдҪҝз”Ёstatic const int a = 0гҖӮеҰӮжһңдёҚйңҖиҰҒеӯҳеңЁдәҺеҶ…еӯҳдёӯпјҢиҝҷдәӣеҸҳйҮҸеңЁиҝҗиЎҢж—¶дёҚдјҡеҚ з”ЁеҶ…еӯҳгҖӮ - Neil Kirk
显示剩余2条评论
3个回答

14

实际上,这比看起来要棘手得多。

明确重申要求:

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

在C++中,表达式可以是左值(lvalue)或右值(prvalue)(在C++11之前和C语言中,rvalue对应相关概念)。左值指向对象,因此它们可以出现在赋值表达式的左侧。我们要避免的是对象存储和左值属性。

你需要的是一个标识符或id-expression评估为prvalue。

目前,只有枚举器可以做到这一点,但正如你观察到的那样,它们留下了一些问题。每个枚举声明都引入了一个新的、独特的类型,因此enum { Value = 123 };引入了一个常量,它不是整数,而是自己独特的类型,可以转换int。虽然它能发挥作用,但并不是最适合这个任务的工具。

你可以使用#define,但这是一个hack,因为它完全避开了解析器。你必须用所有大写字母命名它,然后确保同样的全大写名称没有用于程序中的其他任何东西。对于库接口,这种保证尤其繁琐。

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

constexpr int value() { return 123; }

然而要注意,constexpr函数仍然可以在运行时进行评估。你需要再跨过一个障碍才能将这个值表达为一种计算:

小心使用 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()。函数调用操作符可能看起来不太有效率,但它是目前唯一完全消除存储开销的方法。


1
你是在说简单的 return 123 + 3; 不是在编译时执行的吗? - vsoftco
1
@vsoftco 不完全是这样。但是 constexpr 变量始终保证被视为常量表达式,而 constexpr 函数则不一定,有时可能在运行时计算。当然,对于如此简单的操作,这并没有什么区别。但如果是 int ret = INT_MAX + 3;,那就不同了,因为会发生有符号整数溢出。非常自由地使用 constexpr 有助于防止这种意外情况。 - Potatoswatter
@Potatoswatter - 整数溢出是未定义行为。编译器不需要在运行时执行该操作。 - rustyx
1
@Wormer 123本身就是一个表达式,它具有我们想要的prvalue类别。问题在于将其绑定到可重用名称foo上,使得foo具有与123相同的类别。 - Potatoswatter
1
@Wormer 是的,那里放什么并不重要。我试图在示例中插入一个计算,但有点混乱。 - Potatoswatter
显示剩余5条评论

3

我认为你应该考虑使用C++11的枚举类型指定底层类型这一特性,对于你的示例代码可以这样写:

enum : unsigned int { Value = 123 };

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

-1

以下选择永远不会出错:

static constexpr const unsigned int Value = 123;

说实话,尽量不要在意这个。真的尽量不要。

4
你可以在这里犯很严重的错误。如果在内联函数(如模板)中使用ODR中的常量,将违反一个定义规则,因为该名称在每个翻译单位中都引用了单独的、不同的对象。请注意避免这种情况。 - Potatoswatter
10
C++有53910种定义常量的方式,这让人很难不关注它。 - Neil Kirk
3
如果你是一个有强迫症的完美主义者,这并不会影响你。但是这让我很困扰。 - Neil Kirk
4
如果你有数百个常量,并且你正在编写一个只有几千字节程序空间的嵌入式系统,那么这就成为了一个问题。说 C++ 不用于这种嵌入式编程是循环论证:只要我们注意边角情况,C++ 就能够达到最高效率。 - Potatoswatter
3
@LightnessRacesinOrbit 所以,当问题以抽象的方式陈述时,您会要求提供用例,而当您获得用例时,您会要求讨论抽象机器。??? - Potatoswatter
显示剩余12条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接