web-dev-qa-db-ja.com

ヘッダーで機能しない定数変数

このようにヘッダーに定数変数を定義すると...

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;

できます

誰かが私が間違っているかもしれないことを考えていますか?

ありがとう

55
numerical25

問題は、ヘッダーファイルに外部リンケージを持つオブジェクトdefineがあることです。予想どおり、そのヘッダーファイルを複数の翻訳単位に含めると、外部リンケージを使用して同じオブジェクトの複数の定義を取得しますが、これはエラーです。

それを行う適切な方法は、あなたの意図に依存します。

  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. または、単なる非定義宣言をヘッダーファイルに配置し、definitionsを1つの実装ファイルに配置することもできます。

    headerファイルの宣言には、明示的なexternおよびno initializerを含める必要があります

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

    1つの定義実装ファイルは次のようになります

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

    (上記の宣言が同じ翻訳単位の定義の前にある場合、定義の明示的なexternはオプションです)。

どの方法を選択するかは、意図によって異なります。

最初の方法は、各変換単位で定数の実際の値を確認できるため、コンパイラーがコードを最適化するのを容易にします。ただし、概念的には、すべての翻訳単位で個別の独立した定数オブジェクトを取得します。例えば、 &PIは、各変換単位で異なるアドレスに評価されます。

2番目の方法は、真にglobal定数、つまりプログラム全体で共有される一意の定数オブジェクトを作成します。例えば、 &PIは、各変換単位で同じアドレスに評価されます。ただし、この場合、コンパイラーは、最適化を妨げる可能性のある唯一の変換単位の実際の値しか見ることができません。


C++ 17からは、「両方の長所」を組み合わせた3番目のオプション、インライン変数が得られます。インライン変数は、外部リンケージがあるにもかかわらず、ヘッダーファイルで安全に定義できます。

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; 

この場合、初期化子の値がすべての翻訳単位で表示される名前付き定数オブジェクトを取得します。そして同時に、オブジェクトには外部リンケージがあります。つまり、グローバルアドレスID(&PIはすべての翻訳単位で同じです)。

確かに、そのようなものは一部のエキゾチックな目的(C++のほとんどのユースケースでは最初のバリアントを呼び出す)にのみ必要かもしれませんが、機能はあります。

129
AnT

externは、変数の「実際の」定義が他の場所にあることを意味し、コンパイラは、リンク時に物事が接続されることを信頼する必要があります。 externとインラインで定義することは奇妙であり、プログラムを混乱させているのです。それらをexternにしたい場合は、プログラムの別の場所でexactly onceを定義するだけです。

9
Carl Norum

それらのexternストレージクラスは、ほぼ間違いなくあなたが見ている問題の原因です。削除しても、コードはおそらく問題ありません(少なくともこの点では)。

編集:私はあなたがこれをCとC++の両方としてタグ付けしたことに気付いた。この点で、CとC++は実際にはまったく異なります(ただし、エラーメッセージから、CではなくC++としてコンパイルしているようです)。 C++では、extern変数を(デフォルトで)const変数にstaticストレージクラスがあるため、削除する必要があります。これは、各ソースファイル(翻訳単位)が変数の独自の「コピー」を取得し、異なるファイルの定義間で競合が発生しないことを意味します。 (おそらく)値のみを使用し、変数としては扱わないため、複数の「コピー」を使用しても何も害はありません。ストレージスペースが割り当てられることはありません。

Cでは、externはかなり異なり、externを削除しても、デフォルトではexternになるため、実際の違いはありません。この場合、変数を正確に1か所で初期化し、ヘッダーでexternを宣言する必要があります。または、ヘッダーからstaticを削除した場合/削除した場合に、C++がデフォルトで追加するexternストレージクラスを追加できます。

5
Jerry Coffin

以下の多くの誤った応答。正しいのは、selbibitzeがコメントで正しいと言っているexternを削除するように言っているものです。

これらはconstとして宣言されているため、ヘッダーに定義が含まれていても問題はありません。 C++は、そのアドレス(constへのポインター)を取得しようとしない限り、組み込み型のconstをインライン化します。その場合、staticリンケージを使用してインスタンス化します。 、しかし、同じconstへのすべてのポインタが同じアドレスを持っていると期待しない限り、これは問題ではありません。

2
Clifford

問題は、変数を初期化することですヘッダーファイル内;これによりdefining宣言が作成され、そのヘッダーを含むすべてのファイルで繰り返されるため、多重定義エラーが発生します。

ヘッダーファイルにnon-defining宣言(初期化子なし)が必要であり、実装ファイルのoneに定義宣言を配置します。

2
John Bode

ヘッダーで定数を宣言し、コードファイルの1つで定義する必要があります。どこでも宣言しないと、宣言を実際の定義に結び付けようとしたときにリンカーエラーが発生します。また、#ifdefステートメントを使用して、ヘッダー内に1つの定義を作成することもできます。

それらが必要なすべての人がインクルードするヘッダーで宣言されていることを確認し、一度だけ定義されていることを確認してください。

ジェイコブ

1
TheJacobTaylor

ヘッダーファイルで定数を定義する場合は、static constを使用します。 externを使用する場合、値を割り当てると、ソースファイルを含む各ソースが変数のメモリを提供するため、リンカーは複数の定義について文句を言う権利があります。

1
Christoph

ヘッダーファイルが複数回インクルードされているようです。ガードを追加する必要があります。

各ヘッダーファイルの先頭には、次のようなものがあります。

#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
Peter Alexander

確かに古い質問ですが、役に立つ答えが1つありません。

「ダミー」クラステンプレートで静的な定数をラップするだけで、ヘッダーで静的な定数を受け入れるようにMSVCをだますことができます。

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

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

現在、C <> :: PIは他の場所からアクセスできます。再定義の不満はありません。リンク時間の最適化を行わずに、各コンパイル単位で定数に直接アクセスできます。マクロは、このアプローチをさらに強化するために展開できます(マクロは悪意がある場合でも)。

0
oakad

ヘッダー内でグローバルconstを宣言すると、このhaderを含む各コンパイル単位に同じ名前のグローバル定義が独自に定義されます。その場合、リンカはそれを好みません。

ヘッダーでこれらが本当に必要な場合は、おそらく静的として宣言する必要があります。

0
lollinus