我可以在头文件中像这样定义吗?
constexpr double PI=3.14;
将这个constexpr放在多个cpp文件都会包含的头文件中,是否存在任何问题?
我担心因为标准规定这个constexpr有自己的内存,把它放在头文件中并将头文件添加到多个cpp文件中,会在内存中生成多个相同值的副本和其他一些麻烦。
我正在使用C++11。
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
。
inline
,会出现 ODR(One Definition Rule)问题吗? - Joseph D.inline
。 - Maxim Egorushkininline
变量必须具有相同的地址。constexpr
意味着内部链接。 - Maxim Egorushkinconstexpr
隐含了 inline
(而且在全局/命名空间作用域中,由于 const
隐含了 static
)。但是我很想知道一个静态变量是否必须要求静态存储,即使它永远不会在运行时上下文中使用,例如仅在编译时作为 值 使用的 static constexpr
。换句话说,优化器能否省略静态存储(?) - wardw在 C++17
中,这很清晰。而在 C++11
中,你可以把它包装在一个函数里:
constexpr double PI () { return 3.14; }
inline
的constexpr
仍会生成多个地址并占用多个内存位置,这并不理想。可以通过修改我提供的示例以删除inline
来轻松测试此问题:https://dev59.com/T1UL5IYBdhLWcg3wV22q#57399173 - Ciro Santilli OurBigBook.comconstexpr
确实意味着inline
。对于变量而言,如果从多个翻译单元中包含,没有inline
可能会进一步增加ODR的风险。 - KevinZC++17 inline
变量可运行示例
在此处提到了C++17的inline变量,这里提供一个最小化可运行示例,展示只使用了一个内存位置:
main.cpp
#include <cassert>
#include "notmain.hpp"
int main() {
// Both files see the same memory address.
assert(¬main_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 ¬main_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
C++标准保证地址相同。C++17 N4659标准草案 10.1.6 “内联说明符”:
6 具有外部链接的内联函数或变量在所有翻译单元中具有相同的地址。
cppreference解释说,如果没有使用static
,则具有外部链接。
在GCC 7.4.0、Ubuntu 18.04中测试通过。
C++20 std::math::pi
请注意,对于Pi的特定情况,C++20提供了一个专用的变量模板,如下所示:如何在C++中使用PI常量
我可以在头文件中像这样定义吗?
可以。
如果将其放在头文件中,并将该头文件包含到多个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
一样,只是在编译时而不是由预处理器处理。
constexpr
是独立的。 - Passer Byconst expr
本身,而是它的一种副本。 - user5550963.cpp
文件时,它会为该文件创建一个特定的 constexpr
副本。 - user5550963
constexpr
定义有什么问题(除了拼写错误)? - UnholySheep