2016-04-25 9 views
1

私はこのような何か書きする場合:なぜテンプレートが再定義されないのですか?それはすべてヘッダファイルに書かれているのはなぜですか?

エラーLNK2005: "公共:__thiscall ::のF(ボイド)無効"(F?私のようなリンカエラーを取得する

// A.h 
#ifndef A_h 
#define A_h 
class A 
{ 
public: 
    void f(); 
}; 

void A::f() 
{ 
} 
#endif //A_h 


// B.cpp 
#include "A.h" 

void foo() 
{ 
    A a; 
    a.f(); 
} 


// C.cpp 
#include "A.h" 

void bar() 
{ 
    A b; 
    b.f(); 
} 

// main.cpp 
#include "B.cpp" 
#include "C.cpp" 
using namespace std; 

int main() 
{ 
    foo(); 
    bar(); 
    return 0; 
} 

Aクラスはクラステンプレートであるとき@ B.OBJ

でQAEXXZ @@ A) すでに定義

はなぜ同じ問題は発生しませんか?最終的には、コンパイル時にプレーンなクラス(非テンプレートのクラス)になります。このため、テンプレート以外のクラスと同じ動作、つまりリンカーエラーが予想されます。

+0

タイトルの質問にもかかわらず、これは重複した質問ではありません。これは、ヘッダーにテンプレートコードがあると、重複した定義がリンカーエラーの原因となるのではなく、なぜシンボルが欠落しているのではないのでしょうか。 – Niall

答えて

1

複数の定義については、非テンプレート関数はテンプレートとは異なる扱いをするのはなぜですか?

ここには歴史的な互換性の問題があります。いくつかの要件はCから来ています、それはそれが働く方法です。また、テンプレートとは何か、それらはコードジェネレータに関する理由もあります。必要に応じて、コンパイラはコードを生成する必要があるため、コード生成時にコードを参照する必要があります。これには複数の定義が存在するというノックアウトがあるため、これらの問題を解決するためのルールが必要です。

簡単に言えば、テンプレートはプログラム内で単一の定義を持っているかのように振る舞います(つまり、inlineと宣言されていない) - 特にw.r.t.機能。非テンプレートがinlineと宣言されている場合、同様の現象が発生します。


ここには標準的な参照が含まれています。

いくつかの背景、ここでの問題の大部分は、リンケージとは何ですか、リンケージとは何ですか?

    §3.5/2 [basic.link]

    名前は、それが別のスコープ内の宣言によって導入された名前と同じオブジェクト、参照、関数、タイプ、テンプレート、名前空間または値を示す可能性がある場合リンケージを有すると言われています

  • 名前に外部リンクがある場合、それが表すエンティティは、他の翻訳単位のスコープまたは同じ翻訳単位の他のスコープから名前で参照できます。
  • 名前に内部リンクがある場合、それが表すエンティティは、同じ翻訳単位内の他のスコープの名前で参照できます。
  • 名前にのリンクがない場合、というエンティティは、他のスコープの名前で参照することはできません。

プログラム全体と各翻訳単位の関数と変数に関するいくつかの一般規則。

§3.2/1 [basic.def.odr]

ませ翻訳単位は、任意の 変数、関数、クラス型、列挙型、またはテンプレートの複数の定義を含まないものとします。

そして

§3.2/4 [basic.def.odr]

すべてのプログラムは...

をそのプログラムにODR-使用されているすべての非インライン関数や変数の正確に一つの定義を含まなければなりません§3.2/6 [basic.def.odr]

クラス型(句[クラス])、列挙型([dcl.enum])、外部リンケージ([dcl.fct.spec])を持つインライン関数、クラステンプレート(句[クラステンプレート([temp.static])のメンバ関数、テンプレート([temp.mem.func])のメンバ関数、特定のテンプレートパラメータが指定されていない([temp.spec]、[temp.class.spec])プログラムでは、各定義が異なる翻訳単位で表示され、定義が次の要件を満たしている必要があります。

Dがテンプレートであり、複数の翻訳単位で定義されている場合、前述の要件は、テンプレート定義で使用されるテンプレートの囲みスコープの名前に適用されます([temp.nonde p])、インスタンス化時点の従属名([temp.dep])にも適用されます。 Dの定義がこれらの要件をすべて満たしている場合、その動作は1つの定義がDであるかのようになります。 Dの定義がこれらの要件を満たしていない場合、その動作は定義されていません。

クラスを含む上記のリスト上のいくつかの非公式の観察、テンプレートなどは、これらのことが多い(もちろん排他的ではないか、ヘッダに限る)ヘッダファイルに見られる典型的な要素です。彼らは、すべてが期待どおりに動作するように、これらの特別なルールが与えられています。

クラスメンバー関数についてはどうですか? §9.3 [class.mfct]

1 /メンバ関数を定義することができる([dcl.fct.def])は、インラインメンバ関数([dcl.fct.spec])である場合には、そのクラス定義において、すでに宣言されていてクラス定義で定義されていない場合は、そのクラス定義の外部で定義されている可能性があります。クラス定義の外に出現するメンバ関数定義は、クラス定義を囲む名前空間スコープ内に現れなければならない。...

2 /インラインメンバ関数(静的であれ非静的であれ)クラス定義内の宣言またはクラス定義外の定義を指定すると、関数はinlineまたはconstexprと宣言されます。 [注:名前空間スコープ内のクラスのメンバー関数は、そのクラスのリンケージを持っています。ローカルクラス([class.local])のメンバー関数にはリンケージはありません。 [basic.link]を参照してください。 - エンドノート]

だから基本的には、メンバ関数は、クラス定義で定義されていない暗黙的inlineない、したがって、「通常」のルールが適用されますので、一度だけのプログラムで表示することができます。

テンプレートとは何でしょうかリンケージ?​​3210

テンプレート名にリンケージ([basic.link])があります。内部リンケージを持つテンプレートの特殊化(明示的または暗黙的)は、他の翻訳単位のすべての専門化とは異なります。テンプレート定義は、1つの定義ルール([basic.def.odr])に従います。

+0

インラインで宣言されていることを知らなかったが、間違いなくトリックを行うことができます:D – Narek

+0

暗黙のうちにインライン化されている場合、追加の 'インライン'は意味をなさないでしょう。暗黙のインライン展開についてはわかりません。 Cppreferenceは次のように言います。*リンク時に、異なる翻訳単位によって生成された同一のインスタンスがマージされます。*暗黙的なインライン展開を証明するための標準への参照がありますか? – HolyBlackCat

+0

@holyblackcat。追加のインラインは必要ありません。それが元のポストのポイントでした。私は文言の一部を単純化しました。正式な標準ポストではなく、その一部に沿って進化しました。 – Niall

1

テンプレートはコードではありません。それらはコードを作成するためのパターンです。それらは使用されている場所であればどこでも見ることができなければならないので、コンパイラはそれらを使うための特別な規則を持たなければならない。ここで重要な特別なルールは、テンプレートが使用され、リンカーが重複を無視する場合はいつでも、コンパイラがコードを生成することです。

4

ここに仕事で二つの別々の効果があります。

  1. ラインの外にありますメンバ関数の定義は、通常の関数定義であり、一つの定義規則(ODR)によって、リンクに正確に一度発生する必要があります。定義されたメンバ関数がインライン暗黙inlineあり、ODRは、インライン関数定義を繰り返すことを可能にする:

    すなわち、ヘッダに以下のコードを入れて、それを繰り返し含むことOKです:

    struct Foo { 
        void bar() {} // "inline" implied 
    }; 
    

    しかし、定義がアウトオブラインである場合、それは単一の翻訳単位でなければなりません。

  2. 機能テンプレートは、インラインでない場合でも繰り返し定義できます。テンプレートメカニズムは、一般に、テンプレートの繰り返しインスタンシエーション、およびリンク時の重複排除に対処する必要があります。

    クラステンプレートのメンバー関数自体は関数テンプレートなので、宣言するかどうかは関係ありません。inline

+0

リンク時の重複排除がテンプレート以外のクラスに対して行われないのはなぜですか? – Narek

+0

@Narek:それはいつもそうだったから:-) Cは重複排除を持っていなかったので、C++は同じセマンティクスを引き継いだ。これは、Cには存在しなかった新しい機能(テンプレート)のセマンティクスを変更しただけです。 –

+1

私が尋ねるのは、実装された重複排除機能が通常のクラスにも使用されない理由です。すでに実装されていますよね?ちょうど再利用して、人々の生活を楽にしてください。これは私の元々の質問、ケレックでした。 – Narek

関連する問題