C ++のインラインは完全にオプションではありませんか?

StackOverflow https://stackoverflow.com/questions/908830

  •  05-09-2019
  •  | 
  •  

質問

インラインメンバーがいるクラスがありますが、後でヘッダーから実装を削除したいので、関数のメンバー本文をCPPファイルに移動しました。最初は、ヘッダーファイル(Sloppy Me)にインラインド署名を残したばかりで、プログラムは正しくリンクできませんでした。その後、ヘッダーを修正しましたが、もちろんすべてが正常に機能します。

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

コード:

初め:

//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()
{}

インラインはオプションであると思っていたので、自分のスロップ性について警告を当てるかもしれないと想像しましたが、リンクエラーを期待していませんでした。この場合、コンパイラがすべき正しい/標準的なことは何ですか、標準に応じて私のエラーに値しましたか?

役に立ちましたか?

解決

確かに、インライン関数が言っているこの1つの定義ルールがあります しなければならない 使用されているすべての翻訳ユニットで定義されます。 goryの詳細が続きます。初め 3.2/3:

すべてのプログラムには、そのプログラムで使用されているすべての非インライン関数またはオブジェクトの定義が正確に1つ含まれている必要があります。診断は不要です。この定義は、プログラムに明示的に表示される可能性があります。標準またはユーザー定義のライブラリに表示されるか、(必要に応じて)暗黙的に定義されています(12.1、12.4、12.8を参照)。インライン関数は、使用されているすべての翻訳ユニットで定義されます。

そしてもちろん 7.1.2/4:

インライン関数は、使用されているすべての翻訳ユニットで定義され、すべての場合にまったく同じ定義を持つものとします(3.2)。 [注:翻訳ユニットに定義が表示される前に、インライン関数への呼び出しが発生する場合があります。 ]外部リンケージを持つ関数が1つの翻訳ユニットでインラインと宣言されている場合、表示されるすべての翻訳単位でインラインと宣言されるものとします。診断は必要ありません。外部リンケージを持つインライン関数は、すべての翻訳ユニットで同じアドレスを持つものとします。外部インライン関数の静的ローカル変数は、常に同じオブジェクトを指します。外部インライン関数の文字通りの文字列は、異なる翻訳単位で同じオブジェクトです。

ただし、クラス定義内で関数を定義する場合、それは暗黙的に宣言されています inline 関数。これにより、プログラムにそのインライン関数本体を複数回含むクラス定義を含めることができます。関数があるので external リンケージ、その定義は 同じ 関数(またはそれ以上のgory-同じ entity).

私の主張についてのgoryの詳細。初め 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つの重要なものです。外部インライン関数を複数回定義しますが、それを異なる方法で定義する場合、またはそれを定義し、その中で使用される名前が異なるエンティティに解決する場合、未定義の動作をしています。

機能を使用するすべてのTUで定義する必要があるというルールは、覚えやすいです。そして、それが同じであることも覚えやすいです。しかし、その名前の解決策はどうですか?ここに例がいくつかあります。静的関数を考慮してください assert_it:

static void assert_it() { [...] }

今、それ以来 static 複数の翻訳ユニットに含めると、内部リンケージを与えます。各定義は定義します 異なるエンティティ. 。これはあなたがそうであることを意味します いいえ 使用が許可されています assert_it プログラムで複数回定義される外部インライン関数から:インライン関数が呼ばれる1つのエンティティを参照することが起こるからです assert_it 1つのTuで、しかし別のTuの同じ名前の別のエンティティに。これはすべて退屈な理論であり、コンパイラはおそらく文句を言うことはないでしょうが、特にこの例はODRとエンティティの関係を示していることがわかりました。


以下は、あなたの特定の問題に再び戻ることです。

以下は同じことです:

struct A { void f() { } };
struct A { inline void f(); }; void A::f() { } // same TU!

しかし、関数は非インラインであるため、これは異なります。あなたは複数の定義があるので、あなたはODRに違反します f ヘッダーを複数回含める場合

struct A { void f(); }; void A::f() { } // evil!

あなたが置いても今 inline の宣言について f クラス内で、しかしヘッダーでそれを定義することを省略して、あなたは違反します 3.2/3 (と 7.1.2/4 この翻訳ユニットでは関数が定義されていないため、同じことをもっと詳しく説明します!

C(C99)では、インラインにはC ++とは異なるセマンティクスがあることに注意してください。外部インライン関数を作成する場合、最初に良い論文(できれば標準)を読む必要があります。なぜなら、それらはcで本当に難しいのでTu。cの静的インライン関数は、処理が簡単です。通常の「インライン置換」ヒントを持っていることは別として、他の関数と同じように振る舞います。 inline cとc ++の両方で、インライン分散のヒントとしてのみ機能します。 Staticは(内部リンケージのため)使用されるときにいつでも別のエンティティを作成するため、 inline インラインと整理のヒントを追加するだけです - それ以上ではありません。

他のヒント

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

C ++リンケージは私の専門ではないので、より良い説明のためにリンクを延期します。

代わりに、ただ待つことができます litb 1時間ほどで血みどろの詳細を提供するため;)

指摘する:メソッドがインラインで宣言される場合、その定義は宣言と一緒になければなりません。

harshath.jrの回答に関しては、その定義に「インライン」キーワードがあり、その定義が同じヘッダーで利用可能な場合、メソッドをインラインで宣言する必要はありません。 すなわち:

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

...

それはまったくきれいではありませんが、攻撃的なインラインがデバッグの頭痛になるときに使用されています。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top