头文件中使用constexpr

57

我可以在头文件中像这样定义吗?

 constexpr double PI=3.14;

将这个constexpr放在多个cpp文件都会包含的头文件中,是否存在任何问题?

我担心因为标准规定这个constexpr有自己的内存,把它放在头文件中并将头文件添加到多个cpp文件中,会在内存中生成多个相同值的副本和其他一些麻烦。

我正在使用C++11。


2
constexpr 定义有什么问题(除了拼写错误)? - UnholySheep
4
@UnholySheep,我在问问题!有什么问题吗? - mans
3
你遇到了编译错误还是其他问题?我不太明白,请说明一下具体的问题。我猜你尝试使用它时遇到了一些问题。 - UnholySheep
5
好的 - 这是一个好问题的基础,但你应该将那种细节添加到你的问题正文中,这样人们就知道你真正想问什么了 :) - Lightness Races in Orbit
1
@MaxVollmer 完成了! - mans
显示剩余2条评论
4个回答

89

constexpr表示常量表达式,而在全局/命名空间作用域上的const则意味着static(内部链接),这意味着包括此头文件在内的每个翻译单元都有自己的PI副本。只有在取其地址或引用时才会分配该静态存储器,而每个翻译单元中的地址将不同。

对于const变量暗示了static是为了在C++头文件中使用const来定义常量,而不是使用#define。如果没有static,那么当该头文件被包含在多个翻译单元中且它们被链接在一起时,将出现多个符号定义链接错误。

在C++17中,您还可以将其设置为inline,以便仅在取地址或引用时(即不是static)有一个PI的副本。 inline变量是在C++17中引入的,以允许头文件中具有非const变量定义的仅头库。constexpr适用于静态数据成员时意味着inline,因此不需要inline

换句话说,如果可能的话,在头文件中使用constexpr来定义常量,否则使用const。如果您需要该常量的地址在任何地方都相同,请将其标记为inline


3
如果不使用 inline,会出现 ODR(One Definition Rule)问题吗? - Joseph D.
1
@MaximEgorushkin 我对全局命名空间有点困惑,所以如果我有一个名为foo.h的头文件,并且在其中有一个constexpr auto gFOO = 768;,并且使用#pragma once指令,为了确保所有翻译单元共享这个变量的唯一地址,我需要在C++17中在前面加上inline吗? - johnco3
1
@johnco3 正确,需要使用 inline - Maxim Egorushkin
1
@wardw 只有具有外部链接的 inline 变量必须具有相同的地址。constexpr 意味着内部链接。 - Maxim Egorushkin
更正一下我的评论,我现在意识到 constexpr 隐含了 inline(而且在全局/命名空间作用域中,由于 const 隐含了 static)。但是我很想知道一个静态变量是否必须要求静态存储,即使它永远不会在运行时上下文中使用,例如仅在编译时作为 使用的 static constexpr。换句话说,优化器能否省略静态存储(?) - wardw
显示剩余2条评论

12

C++17 中,这很清晰。而在 C++11 中,你可以把它包装在一个函数里:

constexpr double PI () { return 3.14; }

2
请注意,在C++中,没有inlineconstexpr仍会生成多个地址并占用多个内存位置,这并不理想。可以通过修改我提供的示例以删除inline来轻松测试此问题:https://dev59.com/T1UL5IYBdhLWcg3wV22q#57399173 - Ciro Santilli OurBigBook.com
关于变量,你是正确的,但是对于函数,constexpr确实意味着inline。对于变量而言,如果从多个翻译单元中包含,没有inline可能会进一步增加ODR的风险。 - KevinZ

8

C++17 inline变量可运行示例

此处提到了C++17的inline变量,这里提供一个最小化可运行示例,展示只使用了一个内存位置:

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}

编译并运行:

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main

GitHub源代码

C++标准保证地址相同。C++17 N4659标准草案 10.1.6 “内联说明符”:

6 具有外部链接的内联函数或变量在所有翻译单元中具有相同的地址。

cppreference解释说,如果没有使用static,则具有外部链接。

另请参见:如何声明constexpr extern?

在GCC 7.4.0、Ubuntu 18.04中测试通过。

C++20 std::math::pi

请注意,对于Pi的特定情况,C++20提供了一个专用的变量模板,如下所示:如何在C++中使用PI常量


1

我可以在头文件中像这样定义吗?

可以。

如果将其放在头文件中,并将该头文件包含到多个cpp文件中,会有什么问题吗?

不会。

constexpr变量(int、double等)不占用内存,因此它没有内存地址,编译器会像处理#define一样处理它,用值替换变量。但是对于对象来说就完全不同了。请阅读this以了解更多:

为了避免开销,在大多数情况下,constexpr被替换为其值,但在需要获取constexpr的地址的情况下,编译器会每次分配内存。因此,如果你有一个包含以下内容的ab.h文件:

constexpr double PI = 3.14;

而且你有一个包含以下内容的 a.cpp 文件:

std::cout << PI << "\n";

如果替换PI,则不会分配任何内存。

而如果你有b.cpp

double *MY_PI = &PI;

内存将专门为该实例分配(或者可能是整个b.cpp文件)。

编辑: 感谢@HolyBlackCat及其在下面评论中的代码,似乎内存是按文件分配的。

编辑2: 它是基于文件的。因此,我有一个包含以下内容的constExpr.h

#ifndef CONSTEXPR_H
#define CONSTEXPR_H

#include <iostream>

constexpr int a = 5;
void bb ();
void cc ();

#endif

a.cpp 包含以下内容:

#include <iostream>
#include "constExpr.h"

void aa () {
    std::cout << &a << "\n";
}

int main () {
    aa ();
    bb ();
    cc ();
    return 0;                                                                                                                 
}

并且 b.cpp 包含以下内容:

#include "constExpr.h"

void bb () {
    std::cout << &a << "\n";
}

void cc () {                                                                                                  
    std::cout << &a << "\n";
}

输出是:
0x400930
0x400928
0x400928

结论 但是,说实话,我永远不会像我在示例中所做的那样做某些事情。这对我和我的大脑来说是一个巨大的挑战。constexpr主要是为了取代#define而添加的。正如我们所知道的,由于编译器无法检查#define语句的错误,因此#define很难调试。因此,除非您像上面那样做某些事情,否则它就像#define一样,只是在编译时而不是由预处理器处理。


2
那么你将无法获取它的地址。 - Maxim Egorushkin
5
你刚刚证明了自己的观点是错误的。占用内存和constexpr是独立的。 - Passer By
@路人有点是有点不是。是因为分配了内存,不是因为它是const expr本身,而是它的一种副本。 - user5550963
“local copy” 是什么意思? - HolyBlackCat
@HolyBlackCat 你改变了你的问题 :) 我不知道如何正确地表达,但我的意思是,当你编译 .cpp 文件时,它会为该文件创建一个特定的 constexpr 副本。 - user5550963

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