函数中的静态constexpr变量有意义吗?


192

如果我在函数中有一个变量(例如,一个大数组),则将其声明为staticconstexpr是否有意义?constexpr保证数组是在编译时创建的,那static会没用吗?

void f() {
    static constexpr int x [] = {
        // a few thousand elements
    };
    // do something with the array
}

static根据生成的代码或语义,实际上在做什么?

Answers:


230

简短的答案是,不仅 static有用,而且总是很理想。

首先,请注意staticconstexpr彼此完全独立。static定义对象在执行期间的生存期;constexpr指定在编译期间对象应该可用。在时间和空间上,编译和执行是不相交和不连续的。因此,一旦程序被编译,constexpr就不再相关了。

声明的每个变量constexpr是含蓄const,但conststatic几乎垂直(除了与交互static const整数)。

所述C++对象模型(§1.9)要求所有对象以外位字段占据的存储器的至少一个字节,并具有地址; 此外,在给定时刻在程序中可观察到的所有此类对象必须具有不同的地址(第6段)。这并不完全要求编译器为使用本地非静态const数组的函数的每次调用在堆栈上创建一个新数组,因为as-if只要可以证明没有其他此类对象可以被编译,则编译器可以接受该原则。观察到的。

不幸的是,除非函数是琐碎的(例如,它不会调用其主体在翻译单元中不可见的任何其他函数),否则这很难证明,因为按定义,数组或多或少是地址。因此,在大多数情况下,const(expr)每次调用时都必须在堆栈上重新创建非静态数组,这使在编译时无法对其进行计算的点失去了意义。

另一方面,本地static const对象由所有观察者共享,并且即使从未调用其定义的功能,也可以对其进行初始化。因此,以上都不适用,并且编译器不仅可以自由生成它的单个实例,还可以自由使用。它可以在只读存储中自由生成它的单个实例。

因此,您绝对应该static constexpr在示例中使用。

但是,在某些情况下,您不想使用static constexpr。除非constexpr使用ODR或声明的对象,否则static编译器可以自由地完全不包含它。这非常有用,因为它允许使用编译时临时constexpr数组,而不会用不必要的字节污染已编译的程序。在那种情况下,您显然不想使用static,因为static可能会迫使对象在运行时存在。


2
@AndrewLazarus,您不能抛弃const某个const对象,只能抛弃const X*它指向一个X。但这不是重点。关键是自动对象不能具有静态地址。就像我说的那样,constexpr一旦编译完成就不再有意义了,因此没有任何要抛弃的东西(可能根本没有任何东西,因为甚至不能保证对象在运行时也存在。)
rici

17
我觉得这个答案不仅令人难以置信,而且自相矛盾。例如,您说您几乎总是想要staticconstexpr但要说明它们是正交的且独立的,并且做不同的事情。然后,您要提到一个不合并两者的理由,因为它会忽略ODR的使用(这似乎很有用)。哦,我仍然不明白为什么将constexpr与static一起使用,因为static是用于运行时的东西。您从未解释过为什么constexpr的static很重要。
void.pointer 2014年

2
@ void.pointer:您对最后一段是正确的。我更改了简介。我以为我已经解释了static constexpr它的重要性(它可以防止在每次函数调用时都必须重新创建常量数组),但是我调整了一些词以使其更加清晰。谢谢。
rici 2014年

8
提及编译时间常量与运行时常量也可能很有用。换句话说,如果constexpr常量仅在编译时上下文中使用,而在运行时不再需要,则static没有任何意义,因为到了运行时为止,该值已被有效地“内联”。但是,如果constexpr在运行时上下文中使用(换句话说,constexpr需要将其转换为const隐式,并且可以为运行时代码提供物理地址),它将需要static确保ODR符合性,等等。至少我是这样理解的。
void.pointer 2015年

3
我最近的评论示例:static constexpr int foo = 100;。除非代码做了类似的事情,否则编译器没有理由不能用foo处处代替文字。因此在这种情况下on 没有任何用处,因为在运行时不存在。再次全部取决于编译器。100&foostaticfoofoo
void.pointer 2015年

9

除了给出的答案外,值得注意的是,编译器不需要constexpr在编译时初始化变量,因为知道constexpr和之间的区别static constexpr是使用static constexpr确保您仅将变量初始化一次。

以下代码演示了如何constexpr多次初始化变量(尽管具有相同的值),而如何static constexpr确定只初始化一次。

此外,该码的优点比较constexpr针对const在与组合static

#include <iostream>
#include <string>
#include <cassert>
#include <sstream>

const short const_short = 0;
constexpr short constexpr_short = 0;

// print only last 3 address value numbers
const short addr_offset = 3;

// This function will print name, value and address for given parameter
void print_properties(std::string ref_name, const short* param, short offset)
{
    // determine initial size of strings
    std::string title = "value \\ address of ";
    const size_t ref_size = ref_name.size();
    const size_t title_size = title.size();
    assert(title_size > ref_size);

    // create title (resize)
    title.append(ref_name);
    title.append(" is ");
    title.append(title_size - ref_size, ' ');

    // extract last 'offset' values from address
    std::stringstream addr;
    addr << param;
    const std::string addr_str = addr.str();
    const size_t addr_size = addr_str.size();
    assert(addr_size - offset > 0);

    // print title / ref value / address at offset
    std::cout << title << *param << " " << addr_str.substr(addr_size - offset) << std::endl;
}

// here we test initialization of const variable (runtime)
void const_value(const short counter)
{
    static short temp = const_short;
    const short const_var = ++temp;
    print_properties("const", &const_var, addr_offset);

    if (counter)
        const_value(counter - 1);
}

// here we test initialization of static variable (runtime)
void static_value(const short counter)
{
    static short temp = const_short;
    static short static_var = ++temp;
    print_properties("static", &static_var, addr_offset);

    if (counter)
        static_value(counter - 1);
}

// here we test initialization of static const variable (runtime)
void static_const_value(const short counter)
{
    static short temp = const_short;
    static const short static_var = ++temp;
    print_properties("static const", &static_var, addr_offset);

    if (counter)
        static_const_value(counter - 1);
}

// here we test initialization of constexpr variable (compile time)
void constexpr_value(const short counter)
{
    constexpr short constexpr_var = constexpr_short;
    print_properties("constexpr", &constexpr_var, addr_offset);

    if (counter)
        constexpr_value(counter - 1);
}

// here we test initialization of static constexpr variable (compile time)
void static_constexpr_value(const short counter)
{
    static constexpr short static_constexpr_var = constexpr_short;
    print_properties("static constexpr", &static_constexpr_var, addr_offset);

    if (counter)
        static_constexpr_value(counter - 1);
}

// final test call this method from main()
void test_static_const()
{
    constexpr short counter = 2;

    const_value(counter);
    std::cout << std::endl;

    static_value(counter);
    std::cout << std::endl;

    static_const_value(counter);
    std::cout << std::endl;

    constexpr_value(counter);
    std::cout << std::endl;

    static_constexpr_value(counter);
    std::cout << std::endl;
}

可能的程序输出:

value \ address of const is               1 564
value \ address of const is               2 3D4
value \ address of const is               3 244

value \ address of static is              1 C58
value \ address of static is              1 C58
value \ address of static is              1 C58

value \ address of static const is        1 C64
value \ address of static const is        1 C64
value \ address of static const is        1 C64

value \ address of constexpr is           0 564
value \ address of constexpr is           0 3D4
value \ address of constexpr is           0 244

value \ address of static constexpr is    0 EA0
value \ address of static constexpr is    0 EA0
value \ address of static constexpr is    0 EA0

如您所见,您自己constexpr被多次static初始化(地址不同),而关键字确保初始化仅执行一次。


constexpr const short constexpr_short如果再次初始化constexpr_short,我们可以不给错误
吗?

您的语法constexpr const没有意义,因为constexpr已经是constconst一次或多次添加将被编译器忽略。您正在尝试捕获错误,但这不是错误,这就是大多数编译器的工作方式。
metablaster
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.