実際、インライン関数は、使用されるすべての翻訳単位で定義する必要があるという定義規則が 1 つあります。悲惨な詳細が続きます。最初3.2/3
:
すべてのプログラムには、そのプログラムで使用されるすべての非インライン関数またはオブジェクトの定義が 1 つだけ含まれている必要があります。診断は必要ありません。定義は、プログラム内で明示的に表示されるか、標準またはユーザー定義ライブラリーで見つけることができます。または (適切な場合) 暗黙的に定義されます (12.1、12.4、および 12.8 を参照)。インライン関数は、それが使用されるすべての翻訳単位で定義されます。
そしてもちろん7.1.2/4
:
インライン関数は、それが使用されるすべての翻訳単位で定義され、すべての場合でまったく同じ定義を持つものとします (3.2)。[注: インライン関数の呼び出しは、その定義が翻訳単位に表示される前に発生する場合があります。] 外部リンケージを持つ関数が 1 つの翻訳単位でインライン宣言されている場合、それが現れるすべての翻訳単位でインライン宣言されなければなりません。診断は必要ありません。外部リンケージを持つインライン関数は、すべての翻訳単位で同じアドレスを持つ必要があります。extern インライン関数の静的ローカル変数は、常に同じオブジェクトを参照します。extern inline 関数の文字列リテラルは、異なる翻訳単位の同じオブジェクトです。
ただし、クラス定義内で関数を定義すると、関数として暗黙的に宣言されinline
ます。これにより、そのインライン関数本体を含むクラス定義をプログラムに複数回含めることができます。関数にはexternal
リンケージがあるため、その定義は同じ関数を参照します(または、より悲惨な -同じ を参照しentity
ます)。
私の主張に関する悲惨な詳細。最初3.5/5
:
さらに、クラスの名前に外部リンケージがある場合、メンバー関数、静的データ メンバー、クラス、またはクラス スコープの列挙には外部リンケージがあります。
次に3.5/4
:
名前空間スコープを持つ名前は、[...] 名前付きクラス (第 9 節) の名前、またはクラスがリンケージ目的で typedef 名を持つ typedef 宣言で定義された名前のないクラスの名前である場合、外部リンケージを持ちます。
この「リンケージ目的の名前」は、この楽しいものです。
typedef struct { [...] } the_name;
プログラム内に同じエンティティーの複数の定義があるため、ODR の別のことがたまたま制限されます。3.2/5
退屈なものが続きます。
クラス型 (節 9)、列挙型 (7.2)、外部リンケージを持つインライン関数 (7.1.2) [...] の複数の定義がプログラムに存在する可能性があります。 、および定義が次の要件を満たしていることを条件とします。D という名前のエンティティが複数の翻訳単位で定義されている場合、
- D の各定義は、同じ一連のトークンで構成されます。と
- D の各定義では、対応する名前は、3.4 に従って検索され、D の定義内で定義されたエンティティを参照するか、またはオーバーロードの解決 (13.3) および部分的なテンプレートの特殊化の一致 (14.8) の後に同じエンティティを参照する必要があります。 .3) [...]
今、重要でないものを切り捨てました。上記は、インライン関数について覚えておくべき 2 つの重要なものです。extern インライン関数を複数回定義しても、別の方法で定義する場合、またはそれを定義し、その中で使用される名前が異なるエンティティに解決される場合は、未定義の動作を行っています。
関数が使用されるすべての TU で関数を定義する必要があるという規則は、覚えやすいものです。そして、それが同じであることも覚えやすいです。しかし、その名前解決についてはどうですか? ここにいくつかの例があります。静的関数を考えてみましょうassert_it
:
static void assert_it() { [...] }
これで、static
は内部リンケージを与えるため、複数の翻訳単位に含めると、各定義は異なるエンティティを定義します。これは、プログラム内で複数回定義される予定の extern インライン関数からの使用が許可されていないことを意味します。これは、インライン関数が1 つの TU で呼び出された 1 つのエンティティを参照するが、同じ TU の別のエンティティを参照するためです。別の TU で名前を付けます。これはすべて退屈な理論であり、コンパイラはおそらく文句を言わないことがわかりますが、この例は特に ODR とエンティティの関係を示していることがわかりました。assert_it
assert_it
以下は、あなたの特定の問題に再び戻ることです。
以下は同じものです。
struct A { void f() { } };
struct A { inline void f(); }; void A::f() { } // same TU!
ただし、関数はインラインではないため、これは異なります。f
ヘッダーを複数回含めると、複数の定義があるため、ODR に違反します。
struct A { void f(); }; void A::f() { } // evil!
クラス内inline
での宣言を行った後、ヘッダーでの定義を省略した場合は、関数がその翻訳単位で定義されていないため、違反します(同じことを言いますが、さらに詳しく説明します)。f
3.2/3
7.1.2/4
C (C99) では、インラインのセマンティクスが C++ とは異なることに注意してください。extern インライン関数を作成する場合は、最初に適切な論文 (できれば標準) を読む必要があります。これは、C では非常に扱いにくいためです (基本的に、関数のインライン定義を使用するには、別の非インライン関数定義が必要になります)。 TU. C の静的インライン関数は扱いが簡単です. 通常の「インライン置換」ヒントがあることを除けば、他の関数と同じように動作します. inline
C と C++ の両方の静的関数はインライン置換ヒントとしてのみ機能します.別のエンティティが使用されるたびに (内部リンケージのため)、inline
インライン置換ヒントが追加されるだけで、それ以上ではありません。