32

以下のコードでは、リンク時にFoo::AとFoo::Bのシンボルが未定義になっているため、非常に疲れているか、気付いていない奇妙なことが起こっています。これは、より大きなプロジェクトから可能な限り最小限に抑えられていますが、私が見ているものの本質を示しています。

#include <algorithm>

struct Foo
{
    static const int A = 1;
    static const int B = 2;
};

int main()
{
    return std::min(Foo::A, Foo::B);
}

std :: min関数テンプレートがなくても正常に機能します。つまり、Foo::Aを返すだけです。また、クラス/構造体の外部で静的intを定義する場合も問題ありません(この単純なケースではグローバル)。ただし、このように内部に入るとすぐに、リンカはそれらを見つけることができません。

誰かが何が起こっているのか説明できますか?

4

6 に答える 6

43

定義が必要

あなたが提供したコードは非標準です。const static intメンバーの初期化子をクラスで直接提供できますが、それでも個別の定義を提供する必要があります。それは奇妙で、一種の予想外ですが、あなたはそれを次のように書くことが期待されています:

#include <algorithm>

struct Foo
{
    static const int A = 1;
    static const int B = 2;
};

const int Foo::A;
const int Foo::B;

int main()
{
    return std::min(Foo::A, Foo::B);
}

標準からの引用は、C++のconstおよびstatic指定子の同様の質問にあります。

コードが定義なしで「機能する」ことがあるのはなぜですか?

定義を提供しなくても回避できることが多い理由については、定数式でのみこれらのメンバーを使用している場合、コンパイラーは常にそれらを直接解決し、リンカー解決のためのアクセスが残されません。これは、コンパイラが直接処理できない方法で使用した場合にのみ発生します。そのような場合にのみ、リンカはシンボルが未定義であることを検出します。これはおそらくVisualStudioコンパイラのバグだと思いますが、バグの性質を考えると、これまでに修正されるとは思えません。

ソースが「リンカー」カテゴリに分類される理由は私にはわかりません。それを理解するには、std::minを分析する必要があります。注:GCCを使用してオンラインで試したところ、機能しましたが、エラーは検出されませんでした。

代替:列挙型を使用

別の方法は、列挙型を使用することです。このバージョンは、static const int "inline"イニシャライザー(Visual Studio 6など)をサポートしていない古いコンパイラーを使用する場合にも役立ちます。ただし、std :: minを使用すると、列挙型で他の問題が発生し、明示的なインスタンス化またはキャストを使用するか、Nawazの回答のように1つの名前付き列挙型にAとBの両方を含める必要があることに注意してください。

struct Foo
{
    enum {A = 1};
    enum {B = 2};
};

int main()
{
    return std::min<int>(Foo::A, Foo::B);
}

基準

注:Stroustrup C ++ FAQでさえこれを誤解し、標準のように厳密に定義する必要はありません。

クラス外の定義がある場合にのみ、静的メンバーのアドレスを取得できます。

この定義、9.4.2の標準で要求されています。

C ++ 03の文言:

メンバーがプログラムで使用され、名前空間スコープ定義に初期化子が含まれていない場合でも、メンバーは名前空間スコープで定義されます。

9.4.2のC++11の表現は少し異なります。

3メンバーがプログラムでodr-used(3.2)である場合、メンバーは引き続き名前空間スコープで定義されます。

3.2は、odr-useについて次のように述べています。

3名前が潜在的に評価される式exとして表示される変数xは、xが定数式(5.19)に表示されるための要件を満たすオブジェクトであり、exが式の潜在的な結果のセットの要素である場合を除き、odrで使用されます。 e、ここで、左辺値から右辺値への変換(4.1)がeに適用されるか、eが破棄された値の式です(5節)。

4すべてのプログラムには、そのプログラムでodrで使用されるすべての非インライン関数または変数の定義が1つだけ含まれている必要があります。診断は必要ありません。

私はodrの使用規則を理解していないため、C++11の文言の正確な意味がわからないことを認めなければなりません。

于 2011-02-03T20:07:02.343 に答える
3

整数値が必要な場合は、次のように定義することもできますenum

#include <algorithm>

struct Foo
{
    enum integrals { A = 1, B = 2} ;
};

int main()
{
    return std::min(Foo::A, Foo::B);
}

これで十分です。クラス外での宣言は必要ありません!

オンラインデモ:http ://www.ideone.com/oE9b5

于 2011-02-03T20:14:39.423 に答える
2

クラス定義の外部で静的定数を定義する必要があります。

struct Foo {
    static const int A;
    static const int B;
};

const int Foo::A = 1;
const int Foo::B = 2;
于 2011-02-03T20:06:34.470 に答える
2

ここには良い答えがありますが、もう1つ注意すべき点は、のパラメーターstd::min()が参照であるということです。これは、渡された変数のアドレスを必要とするものであり、これらの変数はコンパイルユニットのオブジェクトファイルに到達しないため、リンカはアドレスを解決できません。

あなたはおそらくこれを最適化されていないビルドで取得していますよね?

最適化を有効にすると、gccではこれが得られないに違いありません。の呼び出しstd::min()はインライン化され、参照はなくなります。

また、を呼び出す直前にとを2つのローカル変数に割り当てるFoo::Aと、この問題も解消されます。Foo::Bstd::min()

これは理想的ではありませんが、この問題の原因となっている変数を定義するコードを所有していない場合は、これを検討することができます。

于 2012-07-12T07:53:41.833 に答える
1

基本的に構造体を名前空間として使用しているので、名前空間だけを使用しないのはなぜですか。

#include <algorithm>

namespace Foo
{
    const int A = 1;
    const int B = 2;
};

int main()
{
    return std::min(Foo::A, Foo::B);
}
于 2011-02-04T02:18:58.597 に答える
0

別の解決策はinlinestatic変数に対するものです。このように、UndefinedSymbolsエラーを排除する最終的な変換ユニットで使用できるようになります。

これはポストC++11AFAIKでのみ機能することに注意してください。

于 2020-05-13T18:13:09.533 に答える