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

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

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" ([email protected]@3NB) already defined in main.obj
1>MyDirectX.obj : error LNK2005: "double const PI_under_180" ([email protected]@3NB) already defined in main.obj
1>MyDirectX.obj : error LNK2005: "double const PI_over_180" ([email protected]@3NB) already defined in main.obj
1>MyGame.obj : error LNK2005: "double const PI" ([email protected]@3NB) already defined in main.obj
1>MyGame.obj : error LNK2005: "double const PI_under_180" ([email protected]@3NB) already defined in main.obj
1>MyGame.obj : error LNK2005: "double const PI_over_180" ([email protected]@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;

Работает

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

Спасибо

Ответ 1

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

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

(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; 

В С++ static необязательно (потому что в С++ const объекты по умолчанию имеют внутреннюю привязку)

const double PI = 3.1415926535; 
const double PI_under_180 = 180.0f / PI; 
const double PI_over_180 = PI/180.0f; 

(2) Или вы можете поместить в заголовочный файл простые неинформационные объявления и поместить определения в один (и только один) файл реализации

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

Ответ 2

extern означает, что "реальное" определение переменной находится в другом месте, и компилятор должен доверять тому, что все будет связано с временем соединения. Наличие определения inline с extern является странным, и это то, что мешает вашей программе. Если вы хотите, чтобы они были extern, просто определите их точно один раз в другом месте вашей программы.

Ответ 3

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

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

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

Ответ 4

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

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

#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;

Ответ 5

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

В заголовочном файле вы хотите указать не определяющее объявление (без инициализатора) и поместить определяющее объявление в один из файлов реализации.

Ответ 6

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

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

Ответ 7

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

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

Jacob

Ответ 8

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

Ответ 9

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

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

Ответ 10

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

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