Постоянные переменные не работают в заголовке
Если я определю мои постоянные переменные в моем заголовке, как это...
extern const double PI = 3.1415926535;
extern const double PI_under_180 = 180.0f / PI;
extern const double PI_over_180 = PI/180.0f;
Я получаю следующую ошибку
1>MyDirectX.obj : error LNK2005: "double const PI" (?PI@@3NB) already defined in main.obj
1>MyDirectX.obj : error LNK2005: "double const PI_under_180" (?PI_under_180@@3NB) already defined in main.obj
1>MyDirectX.obj : error LNK2005: "double const PI_over_180" (?PI_over_180@@3NB) already defined in main.obj
1>MyGame.obj : error LNK2005: "double const PI" (?PI@@3NB) already defined in main.obj
1>MyGame.obj : error LNK2005: "double const PI_under_180" (?PI_under_180@@3NB) already defined in main.obj
1>MyGame.obj : error LNK2005: "double const PI_over_180" (?PI_over_180@@3NB) already defined in main.obj
но если я удаляю эти константы из заголовка и помещаю их в документ, который включает в себя заголовок, как это...
const double PI = 3.1415926535;
const double PI_under_180 = 180.0f / PI;
const double PI_over_180 = PI/180.0f;
Оно работает
У кого-нибудь есть идея, что я могу делать не так??
Спасибо
11 ответов
Проблема в том, что вы определяете объекты с внешней связью в заголовочном файле. Ожидается, что как только вы включите этот заголовочный файл в несколько блоков перевода, вы получите несколько определений одного и того же объекта с внешней связью, что является ошибкой.
Правильный способ сделать это зависит от ваших намерений.
Вы можете поместить свои определения в заголовочный файл, но убедитесь, что они имеют внутреннюю связь.
В C это потребовало бы явного
static
static const double PI = 3.1415926535; static const double PI_under_180 = 180.0f / PI; static const double PI_over_180 = PI/180.0f;
В C++
static
необязательно (потому что в C++const
объекты имеют внутреннюю связь по умолчанию)const double PI = 3.1415926535; const double PI_under_180 = 180.0f / PI; const double PI_over_180 = PI/180.0f;
Или вы можете поместить простые не определяющие объявления в заголовочный файл и поместить определения в один (и только один) файл реализации.
Объявления в заголовочном файле должны включать явное
extern
и нет инициализатораextern const double PI; extern const double PI_under_180; extern const double PI_over_180;
и определения в одном файле реализации должны выглядеть следующим образом
const double PI = 3.1415926535; const double PI_under_180 = 180.0f / PI; const double PI_over_180 = PI/180.0f;
(явный
extern
в определениях является необязательным, если вышеприведенные объявления предшествуют определениям в одной и той же единице перевода).
Какой метод вы выберете, зависит от ваших намерений.
Первый метод облегчает компилятору оптимизацию кода, поскольку он может видеть фактическое значение константы в каждой единице перевода. Но в то же время концептуально вы получаете отдельные, независимые постоянные объекты в каждой единице перевода. Например, &PI
будет оценивать по другому адресу в каждой единице перевода.
Второй метод создает действительно глобальные константы, то есть уникальные константные объекты, которые совместно используются всей программой. Например, &PI
будет оценивать по одному и тому же адресу в каждой единице перевода. Но в этом случае компилятор может видеть фактические значения только в одной и только одной единице перевода, что может помешать оптимизации.
Начиная с C++17, вы получаете третий вариант, который объединяет "лучшее из обоих миров": встроенные переменные. Встроенные переменные могут быть безопасно определены в заголовочных файлах, несмотря на наличие внешних связей
inline extern const double PI = 3.1415926535;
inline extern const double PI_under_180 = 180.0f / PI;
inline extern const double PI_over_180 = PI/180.0f;
В этом случае вы получаете именованный константный объект, значение инициализатора которого видно во всех единицах перевода. И в то же время объект имеет внешнюю связь, т. Е. Имеет глобальную адресную идентичность (&PI
одинаков во всех единицах перевода).
Конечно, что-то подобное может быть необходимо только для некоторых экзотических целей (большинство сценариев использования в C++ требуют первого варианта), но такая возможность есть.
extern
означает, что "реальное" определение переменной находится в другом месте, и компилятор должен верить, что все будет зависеть во время соединения. Наличие определения в соответствии с extern
это странно, и это то, что портит вашу программу. Если вы хотите, чтобы они были extern
просто определите их точно один раз где-нибудь в вашей программе.
extern
класс хранения для них почти наверняка является причиной проблемы, которую вы видите. Если вы удалите его, код, вероятно, будет в порядке (по крайней мере, в этом отношении).
Изменить: я только что заметил, что вы пометили это как C и C++. В этом отношении C и C++ действительно сильно отличаются (но из сообщений об ошибках вы, очевидно, компилируете как C++, а не C). В C++ вы хотите удалить extern
потому что (по умолчанию) const
переменные имеют static
класс хранения. Это означает, что каждый исходный файл (модуль перевода) получит свою собственную "копию" переменной, и не будет никакого конфликта между определениями в разных файлах. Поскольку вы (вероятно) используете только значения, а не рассматриваете их как переменные, наличие нескольких "копий" ничего не повредит - ни одному из них не будет выделено место для хранения.
В С, extern
довольно отличается, и удаление extern
не будет иметь никакого значения, потому что они будут extern
по умолчанию. В этом случае вам действительно нужно инициализировать переменные ровно в одном месте и объявить их extern в заголовке. Кроме того, вы можете добавить static
класс хранения, который C++ добавит по умолчанию, когда / если вы удалите extern
из шапки.
Много неправильных ответов ниже. Правильны те, которые говорят вам, чтобы удалить extern
Как сказал Селлебице, в своем комментарии они верны.
Поскольку они объявлены как const, нет проблем с определением в заголовке. C++ встроит const для встроенного типа, если вы не попытаетесь получить его адрес (указатель на const), и в этом случае он будет создан с помощью static
Кроме того, вы можете также получить несколько экземпляров в отдельных модулях, но если вы не ожидаете, что все указатели на один и тот же const будут иметь один и тот же адрес, это не проблема.
Проблема в том, что вы инициализируете переменные в заголовочном файле; это создает определяющее объявление, которое повторяется в каждом файле, который включает этот заголовок, следовательно, ошибка множественного определения.
Вы хотите объявление без определения (без инициализатора) в файле заголовка и поместите объявление определения в один из файлов реализации.
Вам необходимо объявить константы в заголовке, а затем определить их в одном из ваших файлов кода. Если вы их нигде не объявляете, возникает ошибка компоновщика, когда он пытается связать объявление с фактическим определением. Вы также можете использовать операторы #ifdef, чтобы иметь одно определение в заголовке.
Убедитесь, что они объявлены в заголовке, который включен всеми, кто в них нуждается, и убедитесь, что они определены ровно один раз.
Иаков
Если вы хотите определить константы в заголовочных файлах, используйте static const
, Если вы используете extern
компоновщик вправе жаловаться на множественные определения, потому что каждый, включая исходный файл, будет предоставлять память для переменной, если вы присваиваете значение.
Похоже, что заголовочный файл включается несколько раз. Вам нужно добавить охрану.
В верхней части каждого заголовочного файла у вас должно быть что-то вроде:
#ifndef MY_HEADER_FILE_NAME_H
#define MY_HEADER_FILE_NAME_H
...
// at end of file
#endif
Если вы используете g++ или MSVC, вы можете просто добавить:
#pragma once
В верхней части каждого заголовочного файла, но это не на 100% портативно.
Кроме того, вы не должны определять константы в заголовочных файлах, только объявлять их:
// In header file
extern const int my_const;
// In one source file
const int my_const = 123;
При объявлении global const в заголовке происходит то, что каждый модуль компиляции, включая этот хэдер, будет иметь собственные определения глобальных определений с тем же именем Тогда компоновщик не такой.
Если вы действительно нуждаетесь в них в заголовке, то, вероятно, вы должны объявить их как статические.
Действительно старый вопрос, но один полезный ответ отсутствует.
Можно обмануть MSVC в приеме статических констант в заголовках, просто заключив их в шаблон класса "dummy":
template <typename Dummy = int>
struct C {
static const double Pi;
};
template <typename Dummy = int>
const double C<Dummy>::Pi = 3.14159;
Теперь к C<>::PI можно получить доступ откуда угодно. Переопределение не жалуется; константа напрямую доступна в каждой единице компиляции без оптимизации времени ссылки. Макрос можно развернуть, чтобы еще больше усилить этот подход (хотя макросы - это зло).
Я тоже столкнулся с этой проблемой:
static const uint64 GameTexSignature = 0x0a1a0a0d58455489;
не будет компилироваться в Linux, если он определен в файле заголовка. Он отлично компилируется с MSVC. Исправление, которое сработало для меня, меняет его на:
static constexpr uint64 GameTexSignature = 0x0a1a0a0d58455489;
Это требовалось только для константы uint64, а не для любой из констант uint32. Я думаю, следующее объясняет это, но это означает, что компилятор Linux не считает const uint64 интегральной константой.
https://exceptionshub.com/how-to-declare-a-static-const-char-in-your-header-file.html
Ура, Джон