Использование constexpr в файле заголовка
У меня может быть такое определение в файле заголовка?
constexpr double PI=3.14;
Есть ли проблема в том, что это в файле заголовка, который будет включен в несколько файлов cpp?
Я беспокоюсь, что, поскольку в стандарте говорится, что этот constexpr имеет свою собственную память, помещая его в заголовок и добавляя заголовок в несколько файлов cpp, генерирует несколько копий того же значения в памяти и некоторые другие неприятные проблемы.
Я использую С++ 11
Ответы
Ответ 1
constexpr
подразумевает const
, а const
в глобальной области/пространстве имен подразумевает static
(внутреннюю связь), что означает, что каждая единица перевода, включая этот заголовок, получает свою собственную копию PI
. Память для этой статики будет выделяться только в том случае, если будет взят адрес или ссылка на него, и адрес будет отличаться в каждой единице перевода.
Это подразумевало, что static
для переменных const
было введено специально для использования const
вместо #define
в заголовочных файлах в C++ для определения констант. Без static
была бы ошибка компоновщика множественных определений символов, если этот заголовочный файл включен в более чем одну единицу перевода, которые были связаны вместе.
В C++ 17 вы также можете сделать его inline
, чтобы всегда была единственная копия PI
, если взят адрес или ссылка на него (т.е. не static
). Переменные inline
были введены в C++ 17 для учета библиотек только для заголовков с неконстантными определениями переменных в заголовочных файлах. constexpr
для статических данных подразумевает inline
, поэтому inline
здесь не нужен.
Другими словами, вы должны использовать constexpr
для своих констант в заголовочных файлах, если это возможно, в противном случае const
. И если вам требуется, чтобы адрес этой константы был везде одинаковым, отметьте его как inline
.
Ответ 2
В C++17
вам ясно. В C++11
вы можете заключить его в функцию:
constexpr double PI () { return 3.14; }
Ответ 3
C++ 17 Пример запуска переменной inline
C++ 17 встроенных переменных были упомянуты при: использовании constexpr в заголовочном файле, и вот минимальный исполняемый пример, который показывает, что используется только одна ячейка памяти:
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
GitHub upstream.
Стандарт C++ гарантирует, что адреса будут одинаковыми. C++ 17 N4659 стандартный черновик
10.1.6 "Встроенный спецификатор":
6 Встроенная функция или переменная с внешней связью должны иметь одинаковый адрес во всех единицах перевода.
cppreference https://en.cppreference.com/w/cpp/language/inline объясняет, что если static
не указан, то он имеет внешнюю связь.
См. также: Как объявить constexpr extern?
Протестировано в GCC 7.4.0, Ubuntu 18.04.
C++ 20 std::math::pi
Обратите внимание, что для конкретного случая Pi C++ 20 предлагает специальный шаблон переменной, как показано на: Как использовать константу PI в C++
Ответ 4
У меня может быть такое определение в файле заголовка?
Да
Есть ли проблема в том, что это в файле заголовка, который будет включен в несколько файлов cpp?
Нет
A constexpr
variable (int, double и т.д.) не занимают память, поэтому у него нет адреса памяти, а компилятор обрабатывает его как #define
, он заменяет переменные на значение. Это не относится к объектам, но это совершенно другое. Прочитайте это:
Разрабатывать сделанные замечания. Чтобы избежать накладных расходов, в большинстве случаев constexpr
заменяется его значением, но в случаях, когда вам нужно получить адрес constexpr
, компилятор выделяет память каждый раз. Поэтому, если у вас есть ab.h
, который содержит:
constexpr double PI = 3.14;
и у вас есть a.cpp
, который содержит:
std::cout << PI << "\n";
PI будет заменен, никакая память не будет выделена.
Если у вас есть b.cpp
:
double *MY_PI = &PI;
память будет выделена специально для этого экземпляра (или, может быть, для всего файла b.cpp
).
EDIT:
Благодаря @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
, за исключением того, что он обрабатывается во время компиляции не прекомпилятором.