初めに
ODR(One-definition rule)は、C++の概念です。ODRの定義は[basic.def.odr]にあります。これは、その詳細な定義が必要になったときのための雑な日本語訳です...
なので、特に詳細なものが必要なければ、TL; DRだけ読めばOKです!
TL; DR
英語 wikipedia より:
つまり、ODRは次のように述べています。
- どの翻訳単位でも、テンプレート、型、関数、またはオブジェクトは、複数の定義を持つことができません。これらの中には、宣言をいくつでも持つことができるものがあります。定義はインスタンスを提供します。
- プログラム全体で、オブジェクトまたは非インライン関数は複数の定義を持つことはできません。オブジェクトまたは関数が使用されている場合、それは厳密に1つの定義を持たなければなりません。決して使用されないオブジェクトまたは関数を宣言することができます。その場合は、定義を提供する必要はありません。いかなる場合にも、複数の定義を含めることはできません。
- 型、テンプレート、 外部インライン関数など、いくつかのものは複数の翻訳単位で定義できます。特定のエンティティに対して、各定義は同じである必要があります。異なる翻訳単位の外部以外のオブジェクトおよび関数は、それらの名前とタイプが同じであっても、異なるエンティティです。
- ODRのいくつかの違反は、コンパイラによって診断される必要があります。他の違反、特に翻訳単位に及ぶ違反は、診断する必要はありません。
注:3 は少し奇妙に思われるかもしれませんが、例えば、クラスやテンプレートの定義をヘッダに書いて複数のソースファイルで include されたとき、定義が複数の翻訳単位に出現するのにも関わらず ODR 違反ならない、というものです。(メンバ関数については暗黙に inline となるため ODR に違反しません。)
翻訳
主にgoogle翻訳です。(以下のodr-used について、odr-used されるものは定義が必要になります。)
[basic.def.odr]
段落1
どの翻訳単位にも、変数、関数、クラス型、列挙型、またはテンプレートの定義を複数含めることはできません。
段落2
式が未評価のオペランドまたはその部分式でない限り、式は潜在的に評価されます。
式e
の一連の潜在的な結果は、次のように定義されます。
(2.1)eがid式の場合、集合にはeだけが含まれます。
(2.2)eが配列オペランドを持つ添字演算である場合、集合にはそのオペランドの潜在的な結果が含まれています。
(2.3)eがクラスメンバアクセス式の場合、集合にはオブジェクト式の潜在的な結果が含まれます。
(2.4)eが2番目のオペランドが定数式であるメンバーへのポインター式である場合、その集合にはオブジェクト式の潜在的な結果が含まれます。
(2.5)eの形式が(e1)の場合、集合にはe1の潜在的な結果が含まれます。
(2.6)eがglvalue条件式である場合、その集合は2番目と3番目のオペランドの潜在的な結果の集合の和集合です。
(2.7)eがコンマ式の場合、集合には右側のオペランドの結果が含まれる可能性があります。
(2.8)そうでなければ、集合は空です。
[注:この集合は(ひょっとすると空の)id式の集合で、それぞれがeまたはeの部分式のいずれかです。
[例:次の例では、nのイニシャライザの潜在的な結果の集合には最初のS::x
副次式が含まれますが、2番目のS::x
副次式は含まれません。
struct S { static const int x = 0; };
const int &f(const int &r);
int n = b ? (1, S::x) // S::x は、ここでは odr-used されません
: f(S::x); // S::x は、ここで odr-used されるので、定義が必要です
-終了例]
-エンドノート]
段落3
関数は次の式で命名されます。
(3.1)式に現れる名前の関数は、それが固有の検索結果である場合、または一連のオーバーロードされた関数の選択されたメンバーである場合は、それが純粋仮想関数で、その名前が明示的に修飾されていないか、式がメンバーへのポインターを形成していない限り、その式によって名前が付けられます。
[注:これは、関数のアドレスの取得、名前付き関数の呼び出し、演算子のオーバーロード、ユーザー定義の変換、配置式の代入関数、およびデフォルト以外の初期化について説明しています。
クラス型のオブジェクトをコピーまたは移動するために選択されたコンストラクタは、呼び出しが実際には実装によって排除されていても、式によって名前が付けられていると見なされます。
-エンドノート]
(3.2)クラスの割り当てまたは割り当て解除関数は、[expr.new]および[class.free]で指定されているように、new-expressionによって名前が付けられます。
(3.3)クラスの割り当て解除関数は、[expr.delete]と[class.free]で指定されているように、削除式によって命名されます。
段落4
その名前が潜在的に評価される式exとして現れる変数xは、lvalueからrvalueへの変換をxに適用して、自明な特殊メンバー関数以外の関数を呼び出さない定数式を生成しない限り、exによって使用されます。 xはオブジェクト、exは式eの潜在的な結果の集合の要素です。ここで、左辺値から右辺値への変換がeに適用されるか、eが廃棄値式です。
段落5
構造化バインディングが潜在的に評価される式として現れる場合、その構造化バインディングはodr-usedです。
段落6
これが潜在的に評価される式として現れる場合は、 *thisはodr-usedされます(非静的メンバー関数の本体での暗黙的変換の結果を含む)。
段落7
仮想メンバー関数は、純粋でなければodr-usedされます。
評価される可能性のある式によって名前が付けられる場合、その関数はodr-usedされます。
クラスの非配置割り当てまたは割り当て解除関数は、そのクラスのコンストラクターの定義によってodr-usedされます。
クラスの非配置解除関数は、そのクラスのデストラクタの定義によって、または仮想デストラクタの定義時にルックアップによって選択されることによって、odr-usedされます。
段落8
クラス内の代入演算子関数は、[class.copy.assign]で指定されているように、別のクラスに対して暗黙的に定義されたコピー代入または移動代入関数によってodr-usedされます。
クラスのコンストラクタは[dcl.init]で指定されているようにodr-usedされます。
クラスのデストラクタは、呼び出される可能性がある場合はodr-usedされます。
段落9
次の場合、ローカルエンティティは宣言領域でodr-usableされます。
(9.1)ローカルエンティティが*thisではないか、それを囲むクラスまたは非ラムダ関数パラメータの有効範囲が存在し、そのような最も内側の有効範囲が関数パラメータの有効範囲である場合、それは非静的メンバ関数に対応します。
(9.2)実体が導入された位置とその領域の間の各宣言領域(*thisは、最も内側にあるクラスまたは非ラムダ関数定義の有効範囲内で導入されたと見なされます)。
(9.2.1)中間の宣言領域はブロック有効範囲です。
(9.2.2)介在する宣言領域は、実体の名前を付けた単純キャプチャー、またはキャプチャー・デフォルトを持つラムダ式の関数パラメーター有効範囲です。
ローカルエンティティがodr-useではない宣言領域でodr-usedである場合、プログラムは不適格です。
[例:
void f(int n) {
[] { n = 1; }; // エラー、介在するラムダ式のため、nはodr-usableではありません
struct A {
void f() { n = 2; } // エラー、関数定義スコープが介在しているため、nはodr-usableではありませんdefinition scope
};
void g(int = n); // //エラー、関数パラメータスコープが介在しているため、nはodr-usableではありません
[&] { [n]{ return n; }; }; // OK
}
— 終了例]
段落10
すべてのプログラムは、そのプログラム内で破棄された文の外側で使用されるすべての非インライン関数または変数の定義を1つだけ含みます。 診断は不要です。
定義はプログラム内で明示的に指定することも、標準ライブラリまたはユーザー定義ライブラリで検索することも、(適切な場合)暗黙的に定義されることもあります。
インライン関数またはインライン変数は、破棄された文の外側でodr-usedであるすべての翻訳単位で定義されます。
段落11
クラスが完全であることが要求される方法でクラスが使用されている場合、変換単位にはクラスの定義が1つだけ必要です。
[例:次の完全な翻訳単位は、Xを定義しないにもかかわらず、well-formedです。
struct X; // Xを構造体型として宣言する
struct X* x1; // ポインタ形成にXを使う
X* x2; // ポインタ形成にXを使う
— 終了例][注:宣言と式の規則は、どのコンテキストで完全なクラス型が必要かを記述しています。
次の場合、クラス型Tは完全でなければなりません。
(11.1)型Tのオブジェクトが定義されている、または
(11.2)T型の非静的クラスデータメンバが宣言されている、または
(11.3)Tがnew-expressionで割り当て型または配列要素型として使用されている、または
(11.4)左辺値から右辺値への変換は、T型のオブジェクトを参照するglvalueに適用されます。
(11.5)式が(暗黙的または明示的に)T型に変換された
(11.6)nullポインタ定数ではなく、cv void *以外の型を持つ式が、標準変換、dynamic_cast、またはstatic_castを使用してTへの型ポインタまたはTへの参照に変換されている。
(11.7)クラスメンバアクセス演算子がT型の式に適用されている、または
(11.8)typeid演算子またはsizeof演算子がT型のオペランドに適用されている、または
(11.9)T型の戻り型または引数型を持つ関数が定義されている([basic.def])、または呼び出されている、または
(11.10)T型の基本クラスを持つクラスが定義されている、または
(11.11)T型の左辺値がに割り当てられている、または
(11.12)T型がalignof式の対象です。
(11.13)例外宣言の型はT、Tへの参照、またはTへのポインタです。
-エンドノート]
段落12
次はプログラム内に複数の定義が存在する可能性があります。
- クラス型
- 列挙型
- 外部リンケージを持つインライン関数
- 外部リンケージを持つインライン変数
- クラステンプレート
- 非静的関数テンプレート
- コンセプト
- クラステンプレートの静的データメンバ
- クラステンプレートのメンバ関数
- 一部のテンプレートパラメータが指定されていないテンプレートの特殊化
ただし、各定義が異なる翻訳単位に含まれていて、その定義が次の要件を満たしていることを前提としています。Dという名前の実体が複数の翻訳単位で定義されているとすると、
(12.1)Dの各定義は、同じトークンの並びからなるものとします。 そして
(12.2)Dの各定義において、[basic.lookup]に従って検索された対応する名前は、Dの定義内で定義されたエンティティを指すか、またはオーバーロード解決後およびテンプレート部分特殊化のマッチング後に同じエンティティを指すものとします。
(12.2.1.1) ただし、Dのすべての定義でオブジェクトのリテラル型が同じであれば、名前は内部または非リンケージで不揮発性のconstオブジェクトを参照できます。
(12.2.1.2) ただし、オブジェクトが定数式で初期化されている場合は、名前は内部リンクまたは非リンクの不揮発性constオブジェクトを参照できます。
(12.2.1.3) ただし、オブジェクトの名前がDの定義で使用されていない場合、名前は内部リンクまたはリンクなしの不揮発性constオブジェクトを参照できます。
(12.2.1.4) ただし、オブジェクトがDのすべての定義で同じ値を持つ場合、名前は内部リンクまたは非リンクの不揮発性constオブジェクトを参照できます。
(12.2.2) ただし、参照がDのすべての定義で同じ実体を参照するように、名前が定数式で初期化された内部リンケージまたはリンクなしの参照を指すことができます。
(12.3)Dの各定義において、対応するエンティティは同じ言語リンケージを持つものとします。 そして
(12.4)Dの各定義において、参照されるオーバーロードされた演算子、変換関数、コンストラクター、演算子の新しい関数、および演算子の削除関数への暗黙の呼び出しは、同じ関数、またはDの定義内で定義された関数を指すものとします。 そして
(12.5)Dの各定義において、(暗黙的または明示的な)関数呼び出しによって使用されるデフォルト引数は、そのトークンシーケンスがDの定義内に存在するかのように扱われます。 つまり、デフォルト引数は、この段落で説明されている要件の対象となります(そして、デフォルト引数がデフォルト引数を持つ部分式を持つ場合、この要件は再帰的に適用されます)。 そして
(12.6)Dが暗黙的に宣言されたコンストラクタを持つクラスの場合、それはあたかもそれがodr-usedであるすべての翻訳単位で暗黙的に定義されたかのようであり、すべての翻訳単位の暗黙的定義はDのサブオブジェクトに対して同じコンストラクタを呼び出します
[例:
// 翻訳単位1:
struct X {
X(int, int);
X(int, int, int);
};
X::X(int, int = 0) { }
class D {
X x = 0;
};
D d1; // X(int, int) が D() から呼び出されます
// 翻訳単位2:
struct X {
X(int, int);
X(int, int, int);
};
X::X(int, int = 0, int = 0) { }
class D {
X x = 0;
};
D d2; // X(int, int, int) が D() からよびだされます;
// D() の暗黙の定義はODRに違反しています
— 終了例]
Dがテンプレートであり、複数の翻訳単位で定義されている場合、前述の要件は、テンプレート定義で使用されているテンプレートの包含スコープからの名前と、インスタンス化の時点での従属名の両方に適用されます。
Dの定義がこれらすべての要件を満たす場合、振る舞いはDの単一の定義があるかのようになります。
[注:実体はまだ複数の翻訳単位で宣言されており、[basic.link]はまだこれらの宣言に適用されます。
特に、Dの型に現れるラムダ式は、異なる宣言が異なる型を持つ可能性があります。
- エンドノート]
Dの定義がこれらの要件を満たさない場合、動作は未定義です。
関連するトピック
ODR と直接関係ありませんが、関連するものです
[C++] constexpr関数がインスタンス化されるとき - 地面を見下ろす少年の足蹴にされる私
リンク時に関連するルールの話 - ここは匣
Definitions and ODR - cppreference.com
おわりに
気になる点があれば、編集リクエスト、コメントをいただければ幸いです。