24

2 つの C++ ファイルに同じ名前のクラスの異なる定義がある場合、それらをコンパイルしてリンクすると、警告がなくても何かがスローされます。例えば、

// a.cc
class Student {
public:
    std::string foo() { return "A"; }
};
void foo_a()
{
    Student stu;
    std::cout << stu.foo() << std::endl;
}

// b.cc
class Student {
public:
    std::string foo() { return "B"; }
};
void foo_b()
{
    Student stu;
    std::cout << stu.foo() << std::endl;
}

g++ を使用してコンパイルおよびリンクすると、どちらも "A" を出力します (コマンド ラインの順序で a.cc が b.cc より前にある場合)。

同様のトピックがここにあります。名前空間がこの問題を解決すると思いますが、リンカーが警告を出さない理由がわかりません。また、クラスの 1 つの定義に、別のクラスでは定義されていない追加の関数がある場合、b.cc が次のように更新されたとします。

// b.cc
class Student {
public:
    std::string foo() { return "B"; }
    std::string bar() { return "K"; }
};
void foo_b()
{
    Student stu;
    std::cout << stu.foo() << stu.bar() << std::endl;
}

それから stu.bar() はうまくいきます。このような状況でコンパイラとリンカーがどのように機能するかを教えてくれる人に感謝します。

追加の質問として、クラスがヘッダー ファイルで定義されている場合、そのような状況を回避するために、常に名前のない名前空間でラップする必要がありますか? 副作用はありますか?

4

4 に答える 4

22

これは、1 つの定義規則 (C++03、3.2/5 の「1 つの定義規則」) に違反しています。

各定義が異なる翻訳単位に現れ、定義が次の要件を満たしている場合、プログラム内にクラス型 (第 9 節) の複数の定義が存在する可能性があります。D という名前のエンティティが複数の翻訳単位で定義されている場合、

  • D の各定義は、同じ一連のトークンで構成されます。

1 つの定義規則に違反すると、動作が未定義になります (これは、奇妙なことが起こる可能性があることを意味します)。

リンカはStudent::foo()、a のオブジェクト ファイルに 1 つ、b に 1 つの複数の定義を認識します。ただし、これについて文句はありません。2つのうちの1つを選択するだけです(たまたま、最初に出くわしたもの)。重複する関数のこの「ソフトな」処理は、明らかにインライン関数に対してのみ発生します。非インライン関数の場合、リンカー複数の定義について警告し、実行可能ファイルの生成を拒否します (この制限を緩和するオプションがある場合があります)。GNUldと MSVC のリンカはどちらもこのように動作します。

この動作にはある程度の意味があります。インライン関数は、それらが使用されるすべての翻訳単位で使用できる必要があります。また、一般的には、非インライン バージョンを使用できるようにする必要があります (呼び出しがインライン化されていない場合、または関数のアドレスが取得される場合)。 inlineは、実際には 1 つの定義ルールを自由に通過するだけですが、それが機能するためには、すべてのインライン定義が同じである必要があります。

オブジェクトファイルのダンプを見ると、ある関数が複数の定義を持つことが許可され、他の関数は許可されていないことをリンカがどのように認識しているかを説明する明らかなものは何も見つかりませんが、フラグまたはレコードがあることは確かですまさにそれを行います。残念ながら、リンカーの動作とオブジェクト ファイルの詳細については特に十分に文書化されていないことがわかりました。そのため、正確なメカニズムはおそらく謎のままです。

2番目の質問について:

追加の質問として、クラスがヘッダー ファイルで定義されている場合、そのような状況を回避するために、常に名前のない名前空間でラップする必要がありますか? 副作用はありますか?

ほとんどの場合、これを実行したくない場合は、各クラスが各翻訳単位で異なる型になるため、技術的にクラスのインスタンスをある翻訳単位から別の翻訳単位に渡すことができませんでした (ポインター、参照、またはコピーによって)。また、静的メンバーの複数のインスタンスが発生することになります。それはおそらくうまくいかないでしょう。

それらを異なる名前の名前空間に配置します。

于 2012-05-20T08:39:33.517 に答える
3

クラス定義の 1 つの定義ルールに違反しましたが、言語はこれを禁止しています。コンパイラ/リンカーが警告または診断する必要はありません。また、この場合、そのようなシナリオが期待どおりに機能することは保証されていません。

于 2012-05-20T08:38:41.460 に答える
0

あなたの「余分な」質問は、主な質問の手がかりだと思います。

私があなたの余分な質問を理解していれば、それらを名前空間にラップしたくないと思います。なぜなら、同じクラスを複数の .cc ファイルに #include すると、おそらく各メソッドのコピーを 1 つだけ使用したいからです。それらはクラス内で定義されます。

これは(一種の)メインの例で各関数のバージョンが1つしかない理由を説明しています。リンカーは、2 つの関数が同一であると想定しているだけだと思います。つまり、同じ #include ソースから生成されます。リンカが違うところを検知して警告してくれるといいのですが、なかなか難しいですね。

マーク B が示すように、実際的な答えは「そこには行かないでください」です。

于 2012-05-20T08:40:02.473 に答える
0

1 つの定義規則に違反していることを除けば、C++ での名前マングリングが原因でコンパイラが不平を言うことはありません。

編集:Konrad Rudolphが指摘したように:この場合のマングルされた名前は同じです。

于 2012-05-20T08:48:01.033 に答える