Definitions and ODR (One Definition Rule)
定義 は、宣言によって導入されるエンティティを完全に定義する 宣言 です。以下の場合を除き、すべての宣言は定義です:
- 関数本体を持たない関数宣言:
int f(int); // fを宣言するが、定義はしない
- 初期化子なしの extern ストレージクラス指定子 を持つ宣言、または 言語リンケージ 指定子(例: extern "C" )を持つ宣言:
extern const int a; // aを宣言するが定義はしない extern const int b = 1; // bを定義する
- クラス定義内での 非インライン (C++17以降) staticデータメンバ の宣言:
struct S { int n; // S::n を定義 static int i; // S::i を宣言するが定義はしない inline static int x; // S::x を定義 }; // S を定義 int S::i; // 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; // Sを宣言するが、定義はしない class Y f(class T p); // YとT(およびfとp)を宣言するが、定義はしない
enum Color : int; // declares, but does not define Color |
(C++11以降) |
- テンプレートパラメータの 宣言 :
template<typename T> // Tを宣言するが、定義はしない
- 定義ではない関数宣言内のパラメータ宣言:
int f(int x); // fとxを宣言するが、定義はしない int f(int x) // fとxを定義する { return x + a; }
- A typedef 宣言:
typedef S S2; // S2を宣言するが定義はしない(Sは不完全型でも可)
using S2 = S; // declares, but does not define S2 (S may be incomplete) |
(C++11以降) |
- A using宣言 :
using N::d; // dを宣言するが、定義はしない
|
(C++17以降) |
|
(C++11以降) |
- 空宣言 (いかなるエンティティも定義しない)
- usingディレクティブ (いかなるエンティティも定義しない)
extern template f<int, char>; // declares, but does not define f<int, char> |
(C++11以降) |
- 明示的特殊化 で、宣言が定義ではないもの:
template<> struct A<int>; // A<int>を宣言するが、定義はしない
asm宣言 は何らかのエンティティを定義するものではありませんが、定義として分類されます。
必要に応じて、コンパイラは暗黙的に default constructor 、 copy constructor 、 move constructor 、 copy assignment operator 、 move assignment operator 、および destructor を定義する場合があります。
任意のオブジェクトの定義が incomplete type または abstract class type のオブジェクトとなる場合、プログラムは不適格(ill-formed)である。
目次 |
One Definition Rule
任意の変数、関数、クラス型、列挙型 、コンセプト (C++20以降) またはテンプレートの定義は、1つの翻訳単位につき1つだけ許可されます(これらの一部は複数の宣言を持つことができますが、定義は1つだけ許可されます)。
非 inline 関数または変数で odr-used されるもの(後述)の定義は、プログラム全体(標準ライブラリおよびユーザー定義ライブラリを含む)に厳密に一つだけ存在しなければなりません。コンパイラはこの違反を診断する必要はありませんが、これに違反するプログラムの動作は未定義です。
インライン関数 またはインライン変数 (C++17以降) については、それが odr-used されるすべての翻訳単位で定義が必要です。
クラスに対しては、そのクラスが 完全な型 であることを必要とする方法で使用される場合、定義が必要です。
以下の各要素は、プログラム内で複数の定義を持つことができます:クラス型、列挙型、インライン関数 、インライン変数 (C++17以降) 、 テンプレート化されたエンティティ (テンプレートまたはテンプレートのメンバー、ただし完全な テンプレート特殊化 を除く)。ただし、以下の条件がすべて満たされている場合に限ります:
- 各定義は異なる翻訳単位に現れます。
|
(C++20以降) |
- 各定義は同じトークンのシーケンスで構成される (一般的には、同じヘッダーに現れる)。
- 各定義内からの名前探索は同じエンティティを発見する ( オーバーロード解決 後)、ただし以下の例外を除く:
-
- 内部リンケージまたはリンケージなしの定数は、ODR使用されず、すべての定義で同じ値を持つ限り、異なるオブジェクトを参照してもよい。
|
(since C++11) |
|
(C++11以降) |
- オーバーロードされた演算子(変換関数、アロケーション関数、デアロケーション関数を含む)は、各定義から同じ関数を参照する(定義内で定義されたものを参照する場合を除く)。
- 対応するエンティティは各定義で同じ言語リンケージを持つ(例えば、インクルードファイルが extern "C" ブロック内にない場合)。
-
constオブジェクトがいずれかの定義で 定数初期化 される場合、各定義で定数初期化される。 - 上記の規則は各定義で使用されるすべてのデフォルト引数に適用される。
- 定義が暗黙的に宣言されたコンストラクタを持つクラスの場合、ODR使用されるすべての翻訳単位は、基底クラスとメンバに対して同じコンストラクタを呼び出す必要がある。
|
(C++20以降) |
- 定義がテンプレートの場合、これらの要件はすべて定義時点での名前とインスタンス化時点での依存名の両方に適用されます。
これらの要件がすべて満たされている場合、プログラムは全体で定義が1つしかないかのように動作します。そうでない場合、プログラムは不適格(ill-formed)となり、診断は不要です。
注記: C言語では、型に対するプログラム全体でのODR(One Definition Rule)は存在せず、異なる翻訳単位における同じ変数のextern宣言も、 互換性がある限り 異なる型を持つことが許容されます。C++では、同じ型の宣言で使用されるソースコードのトークンは上述の通り同一でなければなりません:一方の.cppファイルで struct S { int x ; } ; が定義され、他方の.cppファイルで struct S { int y ; } ; が定義された場合、それらをリンクしたプログラムの動作は未定義です。これは通常、 無名名前空間 によって解決されます。
エンティティの命名
変数は、式がそれを表す識別子式である場合、その式によって 命名される 。
関数は以下の場合に式または変換によって named されます:
- 式または変換(名前付き関数、オーバーロードされた演算子、 ユーザー定義変換 、 operator new のユーザー定義配置形式、非デフォルト初期化を含む)として現れる名前の関数は、オーバーロード解決によって選択された場合にその式によって命名されます。ただし、非修飾純粋仮想メンバー関数または純粋仮想関数へのメンバーポインターである場合は除きます。
- クラスの 確保関数 または 解放関数 は、式内に現れる new式 によって命名されます。
- クラスの解放関数は、式内に現れる delete式 によって命名されます。
- オブジェクトをコピーまたはムーブするために選択されたコンストラクタは、 コピー省略 が発生した場合でも、式または変換によって命名されたと見なされます。 一部の文脈でのprvalueの使用はオブジェクトのコピーまたはムーブを伴わない。詳細は 必須省略 を参照。 (C++17以降)
潜在的に評価される式または変換は、関数を名前で参照する場合、その関数をodr-useします。
|
constexpr関数を参照する潜在的に定数評価される式または変換は、その関数を 定数評価に必要なもの とし、式が評価されない場合でも、デフォルト化された関数の定義や関数テンプレート特殊化のインスタンス化を引き起こします。 |
(C++11以降) |
実行結果
E の 潜在的な結果の集合 は、 E 内に現れる識別子式の(空の場合もある)集合であり、以下のように結合されます:
- E が 識別子式 である場合、式 E 自体が唯一の潜在的な結果である。
- E が添字式( E1 [ E2 ] )であり、いずれかのオペランドが配列である場合、そのオペランドの潜在的な結果が集合に含まれる。
- E が E1. E2 または E1. template E2 の形式のクラスメンバアクセス式であり、非静的データメンバを指定している場合、 E1 の潜在的な結果が集合に含まれる。
- E が静的データメンバを指定するクラスメンバアクセス式である場合、そのデータメンバを指定する識別子式が集合に含まれる。
- E が E1. * E2 または E1. * template E2 の形式のメンバへのポインタアクセス式であり、第2オペランドが定数式である場合、 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; // 静的データメンバ // odr-usedされる場合はクラス外での定義が必要 }; const int& f(const int& r); int n = b ? (1, S::x) // S::xはここではodr-usedされない : f(S::x); // S::xはここでodr-usedされる:定義が必要
ODR-use (正式な定義)
変数
x
が
potentially-evaluated expression
expr
によって名前付けされ、それが点
P
に現れる場合、
expr
によってodr-usedされる。ただし、以下のいずれかの条件が満たされる場合は除く:
-
x
は
定数式で使用可能な
参照である(
Pにおいて)。 -
x
は参照ではなく、
(C++26まで)
expr
は式
E
の潜在的な結果の集合の要素であり、以下のいずれかの条件が満たされる:
- E は 破棄値式 であり、左辺値から右辺値への変換が適用されない。
-
x
は
volatileでない
(C++26以降)
オブジェクトであり、
Pにおいて定数式で使用可能で、変更可能な部分オブジェクトを持たず、以下のいずれかの条件が満たされる:
|
(C++26 以降) |
-
-
- E Eは非volatile修飾の非クラス型を持ち、左辺値から右辺値への変換が適用される。
-
struct S { static const int x = 1; }; // S::xへの左辺値から右辺値への変換を適用すると // 定数式が得られる int f() { S::x; // 破棄値式はS::xをodr-useしない return S::x; // 左辺値から右辺値への変換が適用される式は // S::xをodr-useしない }
* this は、 this が潜在的に評価される式として現れる場合(非静的メンバ関数呼び出し式における暗黙の this を含む)に、odr-usedされます。
|
構造化バインディング は、それが評価される可能性のある式として現れる場合、ODR使用されます。 |
(C++17以降) |
関数は以下の場合にodr-usedされます:
- 関数は、(後述の) 潜在的に評価される式または変換によって名前が指定された場合、odr使用されます。
- 仮想メンバー関数 は、純粋仮想メンバー関数でない場合にodr使用されます(仮想関数テーブルを構築するために仮想メンバー関数のアドレスが必要です)。
- クラスの非配置アロケート関数またはデアロケート関数は、そのクラスのコンストラクターの定義によってodr使用されます。
- クラスの非配置デアロケート関数は、そのクラスのデストラクターの定義によって、または仮想デストラクターの定義時点でのルックアップによって選択されることによってodr使用されます。
-
クラス
Tの代入演算子(メンバーまたは基底クラスである場合)は、クラスUの暗黙定義されたコピー代入関数またはムーブ代入関数によってodr使用されます。 - クラスのコンストラクター(デフォルトコンストラクターを含む)は、それを選択する 初期化 によってodr使用されます。
- クラスのデストラクターは、 潜在的に呼び出される可能性がある 場合にodr使用されます。
|
この節は不完全です
理由:ODR使用が影響を与える全ての状況のリスト |
不具合報告
以下の動作変更の欠陥報告書は、以前に公開されたC++規格に対して遡及的に適用されました。
| DR | 適用対象 | 公開時の動作 | 正しい動作 |
|---|---|---|---|
| CWG 261 | C++98 |
多態クラスの解放関数は、プログラム内に関連する
new式やdelete式がなくてもodr-usedされる可能性があった |
odr-useのケースをコンストラクタと
デストラクタをカバーするように補足 |
| CWG 678 | C++98 |
エンティティが異なる言語リンケージを持つ
定義を持つ可能性があった |
この場合の動作は
未定義 |
| CWG 1472 | C++98 |
定数式に現れるための要件を満たす参照変数は、
左辺値から右辺値への変換が直ちに適用される場合でも odr-usedされていた |
この場合には
odr-usedされない |
| CWG 1614 | C++98 | 純粋仮想関数のアドレスを取得するとodr-usedしていた | 関数はodr-usedされない |
| CWG 1741 | C++98 |
潜在的に評価される式内で直ちに左辺値から右辺値へ
変換される定数オブジェクトはodr-usedされていた |
odr-usedされない |
| CWG 1926 | C++98 | 配列添字式はpotential resultsを伝播しなかった | 伝播する |
| CWG 2242 | C++98 |
一部の定義でのみ定数初期化される
const
オブジェクトが
ODRに違反するかどうかが不明確だった |
ODR違反ではない。この場合、オブジェクトは
定数初期化される |
| CWG 2300 | C++11 |
異なる翻訳単位のラムダ式は決して同じクロージャ型を
持つことができなかった |
1定義規則の下でクロージャ型は
同じになり得る |
| CWG 2353 | C++98 |
静的データメンバは、それにアクセスするメンバアクセス式の
potential resultではなかった |
そうである |
| CWG 2433 | C++14 |
変数テンプレートはプログラム内で複数の
定義を持つことができなかった |
持つことができる |
参考文献
- C++23標準 (ISO/IEC 14882:2024):
-
- 6.3 単一定義規則 [basic.def.odr]
- C++20標準 (ISO/IEC 14882:2020):
-
- 6.3 単一定義規則 [basic.def.odr]
- C++17標準 (ISO/IEC 14882:2017):
-
- 6.2 単一定義規則 [basic.def.odr]
- C++14 標準 (ISO/IEC 14882:2014):
-
- 3.2 単一定義規則 [basic.def.odr]
- C++11標準 (ISO/IEC 14882:2011):
-
- 3.2 単一定義規則 [basic.def.odr]
- C++03標準 (ISO/IEC 14882:2003):
-
- 3.2 単一定義規則 [basic.def.odr]
- C++98標準 (ISO/IEC 14882:1998):
-
- 3.2 単一定義規則 [basic.def.odr]