定義とODR (One Definition Rule)
定義とは、宣言されたエンティティを完全に定義する宣言です。次に挙げるものを除き、すべての宣言は定義です。
- 関数本体のない関数宣言
int f(int); // declares, but does not define f
extern const int a; // declares, but does not define a extern const int b = 1; // defines b
- クラス定義内のインラインでない(C++17以降)静的データメンバの宣言
struct S { int n; // defines S::n static int i; // declares, but does not define S::i inline static int x; // defines S::x }; // defines S int S::i; // defines S::i
struct S { static constexpr int x = 42; // implicitly inline, defines S::x }; constexpr int S::x; // declares S::x, not a redefinition |
(C++17以降) |
- クラス名(前方宣言による、または他の宣言で詳細型指定子を使用した宣言)
struct S; // declares, but does not define S class Y f(class T p); // declares, but does not define Y and T (and also f and p)
enum Color : int; // declares, but does not define Color |
(C++11以降) |
- テンプレートパラメータの宣言
template<typename T> // declares, but does not define T
- 定義ではない関数宣言におけるパラメータ宣言
int f(int x); // declares, but does not define f and x int f(int x) // defines f and x { return x + a; }
- typedef宣言
typedef S S2; // declares, but does not define S2 (S may be incomplete)
using S2 = S; // declares, but does not define S2 (S may be incomplete) |
(C++11以降) |
using N::d; // declares, but does not define d
|
(C++17以降) |
|
(C++11以降) |
extern template f<int, char>; // declares, but does not define f<int, char> |
(C++11以降) |
- 定義ではない明示的特化の宣言
template<> struct A<int>; // declares, but does not define A<int>
asm宣言はエンティティを定義しませんが、定義として分類されます。
必要に応じて、コンパイラはデフォルトコンストラクタ、コピーコンストラクタ、ムーブコンストラクタ、コピー代入演算子、ムーブ代入演算子、およびデストラクタを暗黙的に定義する場合があります。
オブジェクトの定義が不完全型または抽象クラス型のオブジェクトを生じさせる場合、プログラムは不正形式となります。
目次 |
[編集] One Definition Rule (単一定義規則)
任意の変数、関数、クラス型、列挙型、コンセプト(C++20以降)、またはテンプレートの定義は、いずれかの翻訳単位で1つだけ許可されます(これらのうちいくつかは複数の宣言を持つことがありますが、定義は1つしか許可されません)。
ODR-use される(後述)インラインでない関数または変数の定義は、プログラム全体(標準ライブラリおよびユーザー定義ライブラリを含む)で1つだけ必要です。コンパイラはこの違反を診断する義務はありませんが、違反したプログラムの動作は未定義です。
インライン関数またはインライン変数(C++17以降)については、ODR-use されるすべての翻訳単位で定義が必要です。
クラスについては、クラスが完全である必要がある方法で使用されるところではどこでも定義が必要です。
次に挙げるもののそれぞれについて、プログラム内に複数の定義が存在しても構いません:クラス型、列挙型、インライン関数、インライン変数(C++17以降)、テンプレートエンティティ(テンプレートまたはテンプレートのメンバ、ただし完全なテンプレート特化ではない)、ただし以下のすべての条件が満たされている場合
- 各定義は異なる翻訳単位に現れる。
| (C++20以降) |
- 内部リンケージまたはリンケージを持たない定数は、ODR-useされず、すべての定義で同じ値を持つ限り、異なるオブジェクトを参照する可能性があります。
|
(C++11以降) |
- 変換、アロケーション、デアロケーション関数を含むオーバーロードされた演算子は、各定義から同じ関数を参照します(定義内で定義されたものを参照する場合を除く)。
- 対応するエンティティは、各定義で同じ言語リンケージを持つ(例: インクルードファイルがextern "C"ブロック内にない)。
constオブジェクトがいずれかの定義で定数初期化された場合、それは各定義で定数初期化されます。- 上記の規則は、各定義で使用されるすべてのデフォルト引数に適用されます。
- 暗黙宣言されたコンストラクタを持つクラスの定義の場合、ODR-useされるすべての翻訳単位は、基底クラスとメンバに対して同じコンストラクタを呼び出す必要があります。
|
(C++20以降) |
- テンプレートの定義の場合、これらの要件はすべて、定義時点の名前と、インスタン化時点の依存名に適用されます。
これらの要件がすべて満たされている場合、プログラムはプログラム全体で1つの定義しか存在しないかのように動作します。そうでない場合、プログラムは不正形式であり、診断は不要です。
注: Cでは、型に対するプログラム全体でのODRは存在せず、異なる翻訳単位での同じ変数のextern宣言でさえ、互換性がある限り異なる型を持つ場合があります。C++では、同じ型の宣言で使用されるソースコードトークンは上記のように同じでなければなりません。ある.cppファイルがstruct S { int x; };を定義し、別の.cppファイルがstruct S { int y; };を定義した場合、それらをリンクするプログラムの動作は未定義です。これは通常、無名名前空間で解決されます。
[編集] エンティティの命名
オブジェクトは、式がそれを名前とする識別子式である場合、その式によって名前が付けられます。
関数は、以下のケースで式または変換によって名前が付けられます。
- 名前が式または変換(名前付き関数、オーバーロードされた演算子、ユーザー定義変換、
operator newのユーザー定義配置形式、非デフォルト初期化を含む)として現れる関数は、オーバーロード解決によって選択された場合にその式によって名前が付けられます。ただし、それが未指定の仮想メンバ関数または純仮想関数へのポインタでない場合を除きます。 - クラスのアロケーションまたはデアロケーション関数は、式に現れるnew式によって名前が付けられます。
- クラスのデアロケーション関数は、式に現れるdelete式によって名前が付けられます。
- オブジェクトをコピーまたはムーブするために選択されたコンストラクタは、コピー省略が発生した場合でも、式または変換によって名前が付けられたと見なされます。一部のコンテキストでのprvalueの使用は、オブジェクトをコピーまたはムーブしません。これは必須の省略を参照してください。(C++17以降)
潜在的に評価される式または変換は、関数を名前とする場合、その関数をODR-useします。
|
定数評価される潜在的な式または変換がconstexpr関数を名前とする場合、式が評価されない場合でも、デフォルトコンストラクタの定義または関数テンプレート特化のインスタン化をトリガーし、それを定数評価に必要なものとします。 |
(C++11以降) |
[編集] 潜在的な結果
式 E の潜在的な結果の集合は、(空である可能性のある)識別子式の集合であり、次のように組み合わされます。
- E が識別子式の場合、式 E がその唯一の潜在的な結果です。
- E が添え字式(E1[E2])で、オペランドのいずれかが配列の場合、そのオペランドの潜在的な結果が集合に含まれます。
- E がクラスメンバアクセス式 E1.E2 または E1.template E2 の形式で、非静的データメンバを名前とする場合、 E1 の潜在的な結果が集合に含まれます。
- E が静的データメンバを名前とするクラスメンバアクセス式の場合、データメンバを指定する識別子式が集合に含まれます。
- E がポインタtoメンバアクセス式 E1.*E2 または E1.*template E2 の形式で、第二オペランドが定数式の場合、 E1 の潜在的な結果が集合に含まれます。
- E が括弧付きの式((E1))の場合、 E1 の潜在的な結果が集合に含まれます。
- E がglvalue条件式(E1 ? E2 : E3、ここで E2 および E3 はglvalue)の場合、 E2 および E3 の潜在的な結果の和集合が集合に含まれます。
- E がカンマ式(E1, E2)の場合、 E2 の潜在的な結果が潜在的な結果の集合に含まれます。
- それ以外の場合、集合は空です。
[編集] ODR-use (非公式定義)
オブジェクトは、その値が読み取られる(コンパイル時定数でない場合)または書き込まれる、そのアドレスが取得される、またはそれに参照が束縛される場合、odr-usedされます。
参照は、使用され、その参照先がコンパイル時に不明な場合、odr-usedされます。
関数は、関数呼び出しが行われるか、そのアドレスが取得される場合、odr-usedされます。
エンティティがodr-usedされる場合、その定義はプログラムのどこかに存在しなければなりません。それに違反すると、通常はリンク時エラーになります。
struct S { static const int x = 0; // static data member // a definition outside of class is required if it is odr-used }; const int& f(const int& r); int n = b ? (1, S::x) // S::x is not odr-used here : f(S::x); // S::x is odr-used here: a definition is required
[編集] ODR-use (公式定義)
点Pに現れる潜在的に評価される式 expr によって名前が付けられる変数 x は、以下のいずれかの条件が満たされている場合を除き、 expr によってodr-usedされます。
- x は、
Pにおいて定数式で使用可能な参照です。 - x は参照ではなく、(C++26まで) expr は式 E の潜在的な結果の集合の要素であり、以下のいずれかの条件が満たされている場合。
- E は破棄値式であり、lvalue-to-rvalue変換が適用されない。
- x は、
Pにおいて定数式で使用可能な非volatileオブジェクトであり、ミュータブルなサブオブジェクトを持たず、以下のいずれかの条件が満たされている場合(C++26以降)(since C++26)。
|
(C++26以降) |
- E はnon-volatile修飾された非クラス型であり、lvalue-to-rvalue変換が適用される。
struct S { static const int x = 1; }; // applying lvalue-to-rvalue conversion // to S::x yields a constant expression int f() { S::x; // discarded-value expression does not odr-use S::x return S::x; // expression where lvalue-to-rvalue conversion // applies does not odr-use S::x }
*this は、this が潜在的に評価される式(非静的メンバ関数呼び出し式における暗黙の this を含む)として現れる場合、odr-usedされます。
|
構造化束縛は、潜在的に評価される式として現れる場合、odr-usedされます。 |
(C++17以降) |
関数は、以下のケースでodr-usedされます。
- 関数は、(後述の)潜在的に評価される式または変換によって名前が付けられた場合、odr-usedされます。
- 仮想メンバ関数は、純粋仮想メンバ関数でない場合、odr-usedされます(vtableを構築するためには仮想メンバ関数のアドレスが必要です)。
- クラスの非配置アロケーションまたはデアロケーション関数は、そのクラスのコンストラクタの定義によってodr-usedされます。
- クラスの非配置デアロケーション関数は、そのクラスのデストラクタの定義によって、または仮想デストラクタの定義時点での検索によって選択された場合にodr-usedされます。
- クラス
Tの代入演算子で、それが他のクラスUのメンバまたは基底である場合、Uの暗黙定義されたコピー代入またはムーブ代入関数によってodr-usedされます。 - クラスのコンストラクタ(デフォルトコンストラクタを含む)は、それを選択する初期化によってodr-usedされます。
- クラスのデストラクタは、潜在的に呼び出される場合、odr-usedされます。
| このセクションは未完成です 理由:odr-useが重要となるすべての状況のリスト |
[編集] 不具合報告
以下の動作変更を伴う欠陥報告が、以前に公開されたC++標準に遡って適用されました。
| DR | 適用対象 | 公開された動作 | 正しい動作 |
|---|---|---|---|
| CWG 261 | C++98 | ポリモーフィッククラスのデアロケーション関数 プログラムに関連する newまたはdelete式がなくてもodr-usedされる可能性があった |
補足された odr-useケースをカバーするために コンストラクタとデストラクタ |
| CWG 678 | C++98 | エンティティが定義を 異なる言語リンケージで持つことができた |
この場合の動作は 未定義となる |
| CWG 1472 | C++98 | 定数式で 出現する条件を満たす参照変数が、lvalue-to-rvalue変換が即座に適用されてもodr-usedされていた lvalue-to-rvalue変換が即座に適用される |
ならない この場合odr-used |
| CWG 1614 | C++98 | 純粋仮想関数のアドレスを取得することがodr-usedしていた | 関数はodr-usedされない |
| CWG 1741 | C++98 | 潜在的に評価される式で即座にlvalue-to-rvalue変換される定数オブジェクトがodr-usedされていた odr-usedされる |
odr-usedされない |
| CWG 1926 | C++98 | 配列添え字式が潜在的な結果を伝播しなかった | 伝播する |
| CWG 2242 | C++98 | 一部の定義でしか 定数初期化されないconstオブジェクトがODRに違反するかどうか不明瞭だった |
ODRは違反しない;その場合オブジェクトは 定数初期化される |
| CWG 2300 | C++11 | 異なる翻訳単位の ラムダ式は同じクロージャ型を持つことができなかった |
クロージャ型は 単一定義規則の下で同じになることができる |
| CWG 2353 | C++98 | 静的データメンバが潜在的な結果ではなかった それをアクセスするメンバアクセス式の |
そうである |
| CWG 2433 | C++14 | 変数テンプレートが プログラム内に複数の定義を持つことができなかった |
できる |
[編集] 参考文献
- C++23標準 (ISO/IEC 14882:2024)
- 6.3 One definition rule [basic.def.odr]
- C++20 standard (ISO/IEC 14882:2020)
- 6.3 One definition rule [basic.def.odr]
- C++17 standard (ISO/IEC 14882:2017)
- 6.2 One definition rule [basic.def.odr]
- C++14 standard (ISO/IEC 14882:2014)
- 3.2 One definition rule [basic.def.odr]
- C++11 standard (ISO/IEC 14882:2011)
- 3.2 One definition rule [basic.def.odr]
- C++03 標準 (ISO/IEC 14882:2003)
- 3.2 One definition rule [basic.def.odr]
- C++98 標準 (ISO/IEC 14882:1998)
- 3.2 One definition rule [basic.def.odr]