ti-enxame.com

variáveis ​​constantes não funcionam no cabeçalho

se eu definir meus constantes no meu cabeçalho assim ...

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

Estou tendo o erro a seguir

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

mas se eu remover essas constantes do cabeçalho e colocá-las no documento que está incluindo o cabeçalho assim ...

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

Funciona

Alguém tem idéia do que eu poderia estar fazendo errado?

Obrigado

53
numerical25

O problema é que você define objetos com ligação externa no arquivo de cabeçalho. Como esperado, depois de incluir esse arquivo de cabeçalho em várias unidades de tradução, você obterá várias definições do mesmo objeto com ligação externa, o que é um erro.

A maneira correta de fazer isso depende da sua intenção.

  1. Você pode colocar suas definições no arquivo de cabeçalho, mas certifique-se de que elas tenham internal linkage. 

    Em C que exigiria um static explícito

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

    Em C++, static é opcional (porque em C++ const os objetos possuem uma ligação interna por padrão)

    const double PI = 3.1415926535; 
    const double PI_under_180 = 180.0f / PI; 
    const double PI_over_180 = PI/180.0f; 
    
  2. Ou você pode colocar meras declarações não definidoras no arquivo de cabeçalho e colocar as definições em um (e apenas um) arquivo de implementação

    As declarações no arquivo header devem incluir um explicativo extern e no initializer

    extern const double PI; 
    extern const double PI_under_180; 
    extern const double PI_over_180; 
    

    e definições em um implementação arquivo deve ser o seguinte

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

    (explícito extern nas definições é opcional, se as declarações acima precederem as definições na mesma unidade de tradução).

Qual método você escolherá depende da sua intenção. 

O primeiro método torna mais fácil para o compilador otimizar o código, já que ele pode ver o valor real da constante em cada unidade de tradução. Mas, ao mesmo tempo, conceitualmente, você obtém objetos constantes independentes e separados em cada unidade de tradução. Por exemplo, &PI avaliará um endereço diferente em cada unidade de tradução.

O segundo método cria constantes verdadeiramente globais , ou seja, objetos constantes exclusivos que são compartilhados por todo o programa. Por exemplo, &PI avaliará o mesmo endereço em cada unidade de tradução. Mas, neste caso, o compilador só pode ver os valores reais em uma e apenas uma unidade de tradução, o que pode impedir as otimizações.


A partir de C++ 17 você obtém a terceira opção, que combina "o melhor dos dois mundos": variáveis ​​inline . Variáveis ​​embutidas podem ser definidas com segurança em arquivos de cabeçalho, apesar de possuírem ligação externa

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; 

Nesse caso, você obtém um objeto constante nomeado cujo valor inicializador é visível em todas as unidades de tradução. E, ao mesmo tempo, o objeto tem ligação externa, ou seja, tem uma identidade de endereço global (&PI é o mesmo em todas as unidades de tradução).

Concedido, algo assim pode ser necessário apenas para alguns propósitos exóticos (a maioria dos casos de uso em C++ exige a primeira variante), mas o recurso está lá.

125
AnT

extern significa que a definição 'real' da variável está em outro lugar, e o compilador deve confiar que as coisas serão conectadas no momento do link. Ter a definição em linha com o extern é estranho e é o que está incrementando o seu programa. Se você quiser que eles sejam extern, apenas os defina exatamente uma vez em outro lugar no seu programa.

9
Carl Norum

A classe de armazenamento extern para eles é quase certamente a causa do problema que você está vendo. Se você removê-lo, o código provavelmente ficará bem (pelo menos a esse respeito).

Edit: Acabei de notar que você marcou isso como C e C + +. A este respeito, C e C++ são realmente muito diferentes (mas a partir das mensagens de erro, você está aparentemente compilando como C++, não como C). Em C++, você deseja remover o extern, porque (por padrão) as variáveis ​​const possuem a classe de armazenamento static. Isso significa que cada arquivo de origem (unidade de tradução) terá sua própria "cópia" da variável e não haverá conflito entre definições em arquivos diferentes. Como você está (provavelmente) usando apenas os valores, não os tratando como variáveis, ter várias "cópias" não afetará nada - nenhuma delas terá espaço de armazenamento alocado.

Em C, extern é bastante diferente, e remover a extern não fará nenhuma diferença real, porque elas serão extern por padrão. Nesse caso, você realmente precisa inicializar as variáveis ​​exatamente em um lugar e declará-las externamente no cabeçalho. Como alternativa, você pode adicionar a classe de armazenamento static que o C++ adicionará por padrão quando/se você remover a extern do cabeçalho.

5
Jerry Coffin

Muitas respostas incorretas abaixo. Os que estão corretos são aqueles que dizem para você remover o extern como o sellibitze também disse em seu comentário estão corretos. 

Porque estes são declarados const, não há problema em ter a definição no cabeçalho. C++ irá embutir uma const para um tipo embutido, a menos que você tente obter seu endereço (um ponteiro para uma const), caso em que irá instanciá-lo com a ligação static, você também poderá obter múltiplas instanciações em módulos separados, mas a menos que você espere todos os ponteiros para o mesmo const para ter o mesmo endereço, isso não é um problema.

2
Clifford

O problema é que você está inicializando as variáveis ​​ no arquivo de cabeçalho ; isso cria uma declaração define , que é repetida em todos os arquivos que incluem esse cabeçalho, daí o erro de múltipla definição.

Você deseja uma declaração de definição de não - (sem inicializador) no arquivo de cabeçalho e coloque a declaração de definição em one dos arquivos de implementação.

2
John Bode

Você precisa declarar os contantes no cabeçalho e depois defini-los em um dos seus arquivos de código. Se você não os declarar em qualquer lugar, haverá um erro de vinculador ao tentar vincular a declaração à definição real. Você também pode usar instruções #ifdef para ter uma definição dentro do cabeçalho.

Certifique-se de que eles sejam declarados em um cabeçalho incluído por todos que precisam deles e certifique-se de que eles sejam definidos exatamente uma vez.

Jacob

1
TheJacobTaylor

Se você quiser definir constantes em arquivos de cabeçalho, use static const. Se você usar extern, o vinculador tem razão em reclamar sobre várias definições, porque cada arquivo de origem incluído fornecerá memória para a variável se você atribuir um valor.

1
Christoph

Parece que o arquivo de cabeçalho está sendo incluído várias vezes. Você precisa adicionar guardas.

No topo de cada arquivo de cabeçalho, você deve ter algo como:

#ifndef MY_HEADER_FILE_NAME_H
#define MY_HEADER_FILE_NAME_H

...

// at end of file
#endif

Se você estiver usando g ++ ou MSVC, basta adicionar:

#pragma once

No topo de cada arquivo de cabeçalho, mas isso não é 100% portátil.

Além disso, você não deve definir constantes em arquivos de cabeçalho, apenas declará-los:

// In header file
extern const int my_const;


// In one source file
const int my_const = 123;
1
Peter Alexander

Uma pergunta antiga, de fato, mas uma resposta útil está faltando.

É possível enganar o MSVC para aceitar constantes estáticas em cabeçalhos simplesmente envolvendo aqueles em um modelo de classe "fictício":

template <typename Dummy = int>
struct C {
     static const double Pi;
};

template <typename Dummy = int>
const double C<Dummy>::Pi = 3.14159;

Agora, C <> :: PI pode ser acessado de outro lugar. Nenhuma redefinição reclama; constante é diretamente acessível em cada unidade de compilação sem otimização de tempo de link fantasia. A macro pode ser lançada para simplificar ainda mais essa abordagem (mesmo que as macros sejam más).

0
oakad

ao declarar const global dentro do cabeçalho faz com que cada unidade de compilação incluindo este hader tenha definições próprias definições globais com o mesmo nome. Então o linker não gosta disso.

Se você realmente precisa deles no cabeçalho, então provavelmente você deve declará-los como estáticos. 

0
lollinus