2

プロジェクトに2つのソースファイルがあり、それぞれが同じ名前のクラスを定義している場合、使用されるクラスのバージョンは何によって決まりますか?

例えば:

// file1.cpp:

#include <iostream>
#include "file2.h"

struct A
{
    A() : a(1) {}
    int a;
};

int main()
{
    // foo() <-- uncomment this line to draw in file2.cpp's use of class A

    A a; // <-- Which version of class A is chosen by the linker?
    std::cout << a.a << std::endl; // <-- Is "1" or "2" output?
}

..。

//file2.h:

void foo();

..。

// file2.cpp:

#include <iostream>
#include "file2.h"

struct A
{
    A() : a(2) {}
    int a;
};

void foo()
{
    A a; // <-- Which version of class A is chosen by the linker?
    std::cout << a.a << std::endl; // <-- Is "1" or "2" output?
}

Aコードを入力する順序を変更するだけで(途中でビルドして)、同じコードで、リンカーによって選択されるさまざまなバージョンを取得することができました。

確かに、同じ名前空間に同じ名前のクラスの異なる定義を含めることは、プログラミングの実践としては不十分です。ただし、リンカーによって選択されるクラスを決定する定義済みのルールはありますか?その場合、それらは何ですか?

この質問への有用な補足として、コンパイラ/リンカがクラスをどのように処理するかを(一般的に)知りたいです-コンパイラは、各ソースファイルをビルドするときに、クラス名とコンパイルされたクラス定義をオブジェクトファイルに組み込みますが、リンカ(名前の衝突のシナリオでは)は、コンパイルされたクラス関数/メンバー定義の1つのセットを破棄しますか?

#included名前の衝突の問題は難解ではありません-ヘッダーのみのテンプレートファイルが2つ以上のソースファイルによって毎回発生することに気付きました(その後、同じテンプレートクラスがインスタンス化され、同じメンバー関数が呼び出されますSTLの一般的なシナリオと同様に、複数のソースファイル)。各ソースファイルには、同じインスタンス化されたテンプレートクラス関数の個別にコンパイルされたバージョンが必要です。したがって、リンカは、リンケージ時にこれらの関数のコンパイルされたバージョンを選択する必要があります)。

--Javaに関する関連質問の補遺-

さまざまな回答が、C ++の単一定義規則( http://en.wikipedia.org/wiki/One_definition_rule )を示していることに注意してください。興味深いことに、Javaにはそのようなルールがないというのは正しいですか?Java仕様によってJavaで複数の異なる定義が許可されていますか?

4

5 に答える 5

3

このようなプログラムは、単一定義規則に違反し、未定義の動作を示します。

プログラム(異なる変換単位またはソースファイル)にクラスまたはインライン関数の定義が複数ある場合は、すべての定義が同一である必要があります。このルールのすべての違反を診断するためにコンパイラーもリンカーも必要ありません(すべての違反を簡単に診断できるわけではありません)。

于 2012-11-14T22:33:49.523 に答える
2

2つのコンストラクターの定義がであることが暗示されているため、これは正常にリンクしているだけinlineです。inlineキーワードを使用せずに、クラスの下に移動してみてください。悪用しているリンケージの種類は、複数の定義が存在することをリンカに通知します。通常、実際に違反している単一定義規則に違反しているとエラーになります。通常、ODRを壊すように見えるこの条件は、異なる翻訳単位で常に複数の同一の定義を持つテンプレートなどに存在します。しかし、それが条件です。異なる翻訳単位の定義は同一でなければなりません。

最終的に、使用するのはコンパイラー次第です。例では。

于 2012-11-14T22:36:49.027 に答える
2

C ++プログラムが同じクラスの2つの定義を提供する場合(つまり、同じ名前空間内で同じ名前が付けられている場合)、プログラムは標準の規則に違反し、未定義の動作が発生します。正確に何が起こるかは、コンパイラとリンカによって異なります。リンカエラーが発生する場合がありますが、これは必須ではありません。

明らかな修正は、競合するクラス名を持たないことです。一意のクラス名を取得する最も簡単な方法は、名前のない名前空間内でローカルに使用される型を定義することです。

// file1.cpp
namespace {
    class A { /*...*/ };
}

// file2.cpp
namespace {
    class A { /*...*/ };
}

これらの2つのクラスは競合しません。

于 2012-11-14T22:37:57.067 に答える
1

許可すると、コンパイラは複数の定義に対して警告を表示します(許可する必要があります)。

gnuリンカーは、コマンドラインにファイルを表示した順序でシンボルを解決するため、最初に表示された定義を使用します。すべてのリンカーが同じように機能するかどうかはわかりません。

于 2012-11-14T22:37:52.553 に答える
1

単一定義規則が存在する理由は、どの定義が使用されているかは関係なく、すべて同一であるためです。どのバージョンが使用されているか、またはそれらが一貫しているかどうかは、問題のコンパイラとリンカに完全に依存します。外部から見える唯一の副作用は、関数内に静的変数がある場合、関数のすべてのインスタンス間で変数の単一のインスタンスを使用する必要があることです。

単一定義規則に違反することにより、正しく記述されたプログラムに関係のない方法でコンパイラー/リンカーのメカニズムを公開することになります。

于 2012-11-14T22:45:48.077 に答える