постоянные переменные не работают в заголовке - PullRequest
54 голосов
/ 24 февраля 2010

если я определю свои постоянные переменные в своем заголовке следующим образом ...

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;

Работает

У кого-нибудь есть идея, что я могу делать неправильно?

Спасибо

Ответы [ 10 ]

127 голосов
/ 24 февраля 2010

Проблема в том, что вы определяете объекты с внешней связью в заголовочном файле. Ожидается, что если вы включите этот заголовочный файл в несколько блоков перевода, вы получите несколько определений одного и того же объекта с внешней связью, что является ошибкой.

Правильный способ сделать это зависит от ваших намерений.

  1. Вы можете поместить свои определения в заголовочный файл, но убедитесь, что они имеют внутреннюю связь.

    В 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; 
    
  2. Или вы можете поместить неопределяющие объявления в заголовочный файл и поместить определения в один (и только один) файл реализации

    Объявления в файле header должны содержать явные 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 ++ требуют первого варианта), но функция есть.

9 голосов
/ 24 февраля 2010

extern означает, что «реальное» определение переменной находится в другом месте, и компилятор должен верить, что все будет подключено во время соединения. Строгое определение с extern - это странно, и это то, что мешает вашей программе. Если вы хотите, чтобы они были extern, просто определите их ровно один раз в другом месте вашей программы.

5 голосов
/ 24 февраля 2010

Класс хранения extern для них почти наверняка является причиной проблемы, которую вы видите. Если вы удалите его, код, вероятно, будет в порядке (по крайней мере, в этом отношении).

Редактировать: я только что заметил, что вы пометили это как C и C ++. В этом отношении C и C ++ действительно сильно отличаются (но из сообщений об ошибках вы, очевидно, компилируете как C ++, а не C). В C ++ вы хотите удалить extern, потому что (по умолчанию) переменные const имеют класс хранения static. Это означает, что каждый исходный файл (модуль перевода) получит свою собственную «копию» переменной, и не будет никакого конфликта между определениями в разных файлах. Поскольку вы (возможно) используете только значения, а не рассматриваете их как переменные, наличие нескольких «копий» ничего не повредит - ни одному из них не будет выделено место для хранения.

В C extern довольно отличается, и удаление extern не будет иметь никакого реального значения, потому что они будут extern по умолчанию. В этом случае вам действительно нужно инициализировать переменные ровно в одном месте и объявить их extern в заголовке. Кроме того, вы можете добавить класс хранения static, который C ++ добавит по умолчанию, когда / если вы удалите extern из заголовка.

2 голосов
/ 25 февраля 2010

Много неправильных ответов ниже. Правильными являются те, которые говорят вам удалить extern, как и в своем комментарии Sellibitze сказал, что это правильно.

Поскольку они объявлены как const, нет проблем с определением в заголовке. C ++ встроит const для встроенного типа, если только вы не попытаетесь получить его адрес (указатель на const), и в этом случае он будет создан с помощью связи static, тогда вы также можете получить несколько экземпляров в отдельных модулях, но если вы ожидаете, что все указатели на один и тот же const будут иметь один и тот же адрес, это не проблема.

2 голосов
/ 24 февраля 2010

Проблема в том, что вы инициализируете переменные в заголовочном файле ; это создает объявление , определяющее , которое повторяется в каждом файле, который содержит этот заголовок, следовательно, ошибка множественного определения.

Требуется объявление определения не (без инициализатора) в файле заголовка и помещение объявления определения в один файлов реализации.

1 голос
/ 24 февраля 2010

Похоже, что заголовочный файл включается несколько раз. Вам нужно добавить охрану.

В верхней части каждого заголовочного файла должно быть что-то вроде:

#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;
1 голос
/ 24 февраля 2010

Если вы хотите определить константы в заголовочных файлах, используйте static const. Если вы используете extern, компоновщик вправе жаловаться на множественные определения, потому что каждое, включая исходный файл, будет предоставлять память для переменной, если вы назначите значение.

1 голос
/ 24 февраля 2010

Вам необходимо объявить константы в заголовке, а затем определить их в одном из ваших файлов кода.Если вы их нигде не объявляете, возникает ошибка компоновщика, когда он пытается связать объявление с фактическим определением.Вы также можете избежать использования операторов #ifdef, чтобы иметь одно определение в заголовке.

Убедитесь, что они объявлены в заголовке, который включен всеми, кто в них нуждается, и убедитесь, что они определены ровно один раз.

Джейкоб

0 голосов
/ 24 октября 2013

Действительно старый вопрос, но один полезный ответ отсутствует.

Можно заставить 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 можно получить доступ из других мест. Переопределение не жалуется; константа напрямую доступна в каждой единице компиляции без оптимизации времени ссылки. Макрос можно использовать для дальнейшей оптимизации этого подхода (хотя макросы являются злыми).

0 голосов
/ 24 февраля 2010

при объявлении глобального константы в заголовке приводит к тому, что каждый модуль компиляции, включая этот хэдер, будет иметь собственные определения глобальных определений с тем же именем. Тогда компоновщик не такой.

Если вы действительно нуждаетесь в них в заголовке, то, вероятно, вы должны объявить их как статические.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...