20

インライン メンバーを持つクラスがありますが、後でヘッダーから実装を削除することにしたので、関数のメンバー本体を cpp ファイルに移動しました。最初はヘッダー ファイルにインライン署名を残しただけで (ずさんな私)、プログラムは正しくリンクできませんでした。その後、ヘッダーを修正しましたが、もちろんすべて正常に動作します。

しかし、インラインは完全にオプションではありませんでしたか?

コード内:

初め:

//Class.h
class MyClass
{
   void inline foo()
   {}
};

次に変更 (リンクしません):

//Class.h
class MyClass
{
   void inline foo();
};

//Class.cpp
void MyClass::foo()
{}

そして、(正常に動作します):

//Class.h
class MyClass
{
   void foo();
};

//Class.cpp
void MyClass::foo()
{}

インラインはオプションだと思っていたので、自分のずさんさに対する警告でなんとかなるだろうと想像していましたが、リンク エラーが発生するとは予想していませんでした。この場合、コンパイラがすべき正しい/標準的なことは何ですか?標準に従ってエラーに値しましたか?

4

4 に答える 4

38

実際、インライン関数は、使用されるすべての翻訳単位で定義する必要があるという定義規則が 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_itassert_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での宣言を行った後、ヘッダーでの定義を省略した場合は、関数がその翻訳単位で定義されていないため、違反します(同じことを言いますが、さらに詳しく説明します)。f3.2/37.1.2/4

C (C99) では、インラインのセマンティクスが C++ とは異なることに注意してください。extern インライン関数を作成する場合は、最初に適切な論文 (できれば標準) を読む必要があります。これは、C では非常に扱いにくいためです (基本的に、関数のインライン定義を使用するには、別の非インライン関数定義が必要になります)。 TU. C の静的インライン関数は扱いが簡単です. 通常の「インライン置換」ヒントがあることを除けば、他の関数と同じように動作します. inlineC と C++ の両方の静的関数はインライン置換ヒントとしてのみ機能します.別のエンティティが使用されるたびに (内部リンケージのため)、inlineインライン置換ヒントが追加されるだけで、それ以上ではありません。

于 2009-05-26T13:29:20.590 に答える
10

メソッドが実際にインライン化されるかどうかは、コンパイラの独自の裁量です。ただし、インライン キーワードの存在は、メソッドのリンケージにも影響します。

C++ リンケージは私の専門ではないので、より良い説明についてはリンク先を参照してください。

または、 litbが 1 時間ほどで悲惨な詳細を提供するのを待つこともできます;)

于 2009-05-26T03:39:23.850 に答える
6

注意点: メソッドがインラインで宣言されている場合、その定義はその宣言と一緒でなければなりません。

于 2009-05-26T04:24:08.903 に答える
2

harshath.jrの答えに関しては、その定義に「inline」キーワードがあり、その定義が同じヘッダーで使用できる場合、メソッドをインラインで宣言する必要はありませ

class foo
{
  void bar();
};

inline void foo::bar()
{
  ...
}

これは、ビルドが「デバッグ」であるか「リリース」であるかに応じて、次のようにメソッドを条件付きでインライン化する場合に役立ちます。

// Header - foo.h

class foo
{
  void bar();  // Conditionally inlined.
};

#ifndef FOO_DEBUG
# include "foo.inl"
#endif

「インライン」ファイルは次のようになります。

// Inline Functions/Methods - foo.inl
#ifndef FOO_DEBUG
# define FOO_INLINE inline
#else
# define FOO_INLINE
#endif

FOO_INLINE void foo::bar()
{
  ...
}

実装は次のようになります。

// Implementation file - foo.cpp
#ifdef FOO_DEBUG
# include "foo.inl"
#endif

...

正確にはきれいではありませんが、アグレッシブインラインがデバッグの頭痛の種になる場合に使用されます。

于 2009-05-26T20:28:33.783 に答える