静的クラスメンバーへの未定義の参照


201

次のコードがコンパイルされない理由を誰かが説明できますか?少なくともg ++ 4.2.4では。

さらに興味深いことに、MEMBERをintにキャストするとコンパイルされるのはなぜですか?

#include <vector>

class Foo {  
public:  
    static const int MEMBER = 1;  
};

int main(){  
    vector<int> v;  
    v.push_back( Foo::MEMBER );       // undefined reference to `Foo::MEMBER'
    v.push_back( (int) Foo::MEMBER ); // OK  
    return 0;
}

<pre> <code> </ code> </ pre>を使用する代わりに、質問を編集してコードを4つのスペースでインデントしました。つまり、山括弧はHTMLとして解釈されません。
スティーブジェソップ

stackoverflow.com/questions/16284629/…この質問を参照できます。
Tooba Iqbal 2018

回答:


199

静的メンバーを実際にどこかに定義する必要があります(クラス定義の後)。これを試して:

class Foo { /* ... */ };

const int Foo::MEMBER;

int main() { /* ... */ }

これにより、未定義の参照が取り除かれます。


3
良い点は、インラインの静的なconst整数の初期化は、アドレスを取得できないスコープ付き整数定数を作成し、ベクトルは参照パラメーターを取得することです。
エヴァン・テラン

10
この回答では、質問の最初の部分のみを扱います。2番目の部分ははるかに興味深いものです。なぜNOPキャストを追加すると、外部宣言がなくても機能するのですか?
nobar

32
クラス定義がヘッダーファイルにある場合、静的変数の割り当てはヘッダーではなく実装ファイルにある必要があることを理解するのに少し時間を費やしました。
シャネット2012

1
@shanet:非常に良い点-私は私の答えでそれを述べるべきだった!
ドリューホール

しかし、それをconstとして宣言した場合、その変数の値を変更することはできませんか?
Namratha 2012年

78

問題は、新しいC ++機能の興味深い衝突と、ユーザーが実行しようとしていることが原因です。まず、push_back署名を見てみましょう:

void push_back(const T&)

タイプのオブジェクトへの参照が必要Tです。古い初期化システムでは、そのようなメンバーが存在します。たとえば、次のコードは正常にコンパイルされます。

#include <vector>

class Foo {
public:
    static const int MEMBER;
};

const int Foo::MEMBER = 1; 

int main(){
    std::vector<int> v;
    v.push_back( Foo::MEMBER );       // undefined reference to `Foo::MEMBER'
    v.push_back( (int) Foo::MEMBER ); // OK  
    return 0;
}

これは、その値が格納されている実際のオブジェクトがどこかにあるためです。ただし、上記のような静的constメンバーを指定する新しいメソッドに切り替えるとFoo::MEMBER、オブジェクトではなくなります。これは一定で、次のようなものです。

#define MEMBER 1

しかし、プリプロセッサマクロの頭痛の種はありません(そして、タイプセーフがあります)。つまり、参照を期待しているベクトルは、参照を取得できません。


2
おかげで、助けになりました... stackoverflow.com/questions/1995113/strangest-language-featureがまだ存在しない場合は、それを利用できます...
Andre Holzner

1
また、MSVCは非キャストバージョンを問題なく受け入れることにも注意してください。
ポージュ

4
-1:これは単に真実ではありません。静的メンバーをインラインで初期化することは、どこかでodrを使用するときに定義する必要があります。コンパイラーの最適化によってリンカーエラーが解消される場合もありますが、変更はありません。この場合、左辺値から右辺値への変換((int)キャストのおかげで)は、定数の完全な可視性を持つ変換単位で行われ、Foo::MEMBERはもはやodr-usedではありません。これは、参照が渡されて別の場所で評価される最初の関数呼び出しとは対照的です。
2014

どうvoid push_back( const T& value );ですか?const&は右辺値とバインドできます。
Kostas

59

C ++標準では、何らかの方法で定義が必要な場合、静的constメンバーの定義が必要です。

たとえば、アドレスが使用される場合など、定義は必須です。 push_backはconst参照によってパラメータを取得するため、コンパイラは厳密にメンバーのアドレスを必要とし、名前空間で定義する必要があります。

明示的に定数をキャストすると、テンポラリが作成され、リファレンスにバインドされるのはこのテンポラリです(標準の特別なルールの下で)。

これは本当に興味深いケースであり、stdを変更して定数メンバーに対して同じ動作をさせるには、問題を提起する価値があると私は実際に考えています!

ただし、奇妙な方法で、これは単項「+」演算子の正当な使用と見なすことができます。基本的にunary +はの値は右辺値であるため、右辺値をconst参照にバインドするルールが適用され、静的なconstメンバーのアドレスは使用しません。

v.push_back( +Foo::MEMBER );

3
+1。はい、タイプTのオブジェクトxの場合、式 "(T)x"を使用してconst refをバインドできますが、プレーンな "x"はバインドできません。「単項+」についてのあなたの観察が大好きです!貧しい小さな「単項+」が実際に使用されたと誰が思ったでしょう... :)
j_random_hacker 2009年

3
一般的なケースについて考えます... C ++には、(1)定義されている場合にのみ左辺値として使用できるが、(2)なくても右辺値に変換できるというプロパティを持つオブジェクトが他にありますか?定義された?
j_random_hacker 2009年

良い質問です。少なくとも現時点では、他の例は考えられません。委員会はほとんど既存の構文を再利用しただけなので、これはおそらくここだけです。
Richard Corden、

@RichardCorden:単項+はそれをどのように解決しましたか?
Blood-HaZaRd

1
@ Blood-HaZaRd:右辺値参照の前の唯一のオーバーロードはpush_backでしたconst &。メンバーを直接使用すると、メンバーが参照にバインドされ、アドレスが必要でした。ただし、を追加する+と、メンバーの値を持つ一時が作成されます。参照は、メンバーにアドレスを要求するのではなく、一時的にバインドします。
Richard Corden

10

Aaa.h

class Aaa {

protected:

    static Aaa *defaultAaa;

};

Aaa.cpp

// You must define an actual variable in your program for the static members of the classes

static Aaa *Aaa::defaultAaa;

1

キャストが機能する理由はわかりませんが、Foo :: MEMBERは、Fooが初めて読み込まれるまで割り当てられません。また、Fooを読み込まないため、割り当てられません。どこかにFooへの参照があれば、おそらく機能します。


私はあなたがあなた自身の質問に答えていると思います:キャストは(一時的な)参照を作成するので機能します。
Jaap Versteegh、2011年

1

C ++ 11では、上記のような基本的な型が可能になります。

class Foo {
public:  
  static constexpr int MEMBER = 1;  
};

constexpr一部では、静的作成式を静的のとは対照的に、変数だけの非常にシンプルなインラインメソッドの定義のようにその振る舞いをして- 。ただし、このアプローチは、テンプレートクラス内のC文字列constexprsを使用すると少し不安定になります。


「static const int MEMBER = 1;」なので、これは私にとって不可欠であることがわかりました。スイッチでMEMBERを使用するにはMEMBERが必要ですが、それをベクトルで使用するには外部宣言が必要であり、同時に両方を持つことはできません。しかし、あなたはここに与える式はありません、少なくとも私のコンパイラとの両方のための仕事を、。
ベンファーマー

0

2番目の質問について:push_refは参照をパラメーターとして受け取り、クラス/構造体の静的constメンバーへの参照を持つことはできません。static_castを呼び出すと、一時変数が作成されます。そして、このオブジェクトへの参照を渡すことができ、すべてがうまくいきます。

または、少なくともこれを解決した私の同僚はそう言った。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.