Использование 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(&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 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, за исключением того, что он обрабатывается во время компиляции не прекомпилятором.