web-dev-qa-db-ja.com

クラスメンバー関数がインライン化されているのはなぜですか?

私の質問は以前にここで尋ねられたと思います。私はそれらを読みましたが、それでも少し混乱しているので、それを明確にするように求めています。

The C++ standard says all member functions defined inside class definition are inline

また、コンパイラは関数のインライン化を無視できると聞きました。上記の場合に当てはまりますか、それともクラス定義内で定義されている場合は常にインライン化されますか?

また、クラス定義内で定義されたすべての関数をインライン化するこの設計の背後にある理由は何でしたか?そして、インライン化はソースファイルとヘッダーファイルとどのような関係がありますか?

pdate:インライン化しない場合は、常にクラス外で関数を定義する必要がありますよね?

JohnBによる更新2:クラス定義内で宣言された2つの関数は、それぞれが他の関数の本体全体を含む必要があるため、相互に呼び出すことはできませんでした。この場合はどうなりますか? (Emilio Garavagliaはすでに回答しています)

31
vidit

インラインには2つの効果があるため、混乱が生じます。

  1. これは、関数コードが、効果的に呼び出されるではなく、関数が呼び出される場所展開になる可能性があることをコンパイラーに通知します。
  2. 関数定義を繰り返すことができることをコンパイラーに通知します。

ポイント1は、コンパイラがコードを最適化するために実際に好きなことを実行できるという意味で「古風な」ものです。実行できる場合は常にマシンコードを「インライン化」し、実行できない場合は決して実行しません。

ポイント2は、この用語の実際の意味です。ヘッダーで関数をdefine(本文を指定)する場合、ヘッダーはより多くのソースに含めることができるため、リンカーに通知するようにコンパイラーに指示する必要があります。定義が重複しているため、マージできます。

現在、言語仕様により、フリー関数(クラス本体で定義されていない)はデフォルトでインラインとして定義されていないため、ヘッダーで次のように定義します

_void myfunc()
{}
_

ヘッダーがより多くのソースに含まれ、同じ出力にリンクされている場合、リンカーは複数の定義エラーを報告するため、次のように定義する必要があります。

_inline void fn()
{}
_

クラスメンバーの場合、デフォルトは逆です。宣言するだけでは、インライン化されません。それらを定義すると、インラインになります。

したがって、ヘッダーは次のようになります。

_//header file

class myclass
{
public:
    void fn1()
    {} //defined into the class, so inlined by default

    void fn2();
};

inline void myclass::fn2()
{} //defined outside the class, so explicit inline is needed
_

そして、myclass::fn2()定義が適切なソースに入った場合、inlineキーワードを失う必要があります。

39

inlineキーワードには、関数2の意味があります。

  1. コード置換inline関数が呼び出される場合は常に、その関数呼び出しを生成せず、関数の内容をその呼び出しの場所に配置するだけです(これは次のようなものです)。マクロ置換、ただしタイプセーフ)
  2. 1つの定義ルールinline関数に対して複数の定義を生成せず、すべてに共通の単一の定義のみを生成します(例外:static関数)

最初の用語(「コード置換」)は、コンパイラーにとって単純に要求です。コンパイラはテキストを配置するか関数呼び出しを配置するかを判断する方がよいため、これは無視できます。 (たとえば、virtual関数または再帰関数をインライン化することはできません)。

2番目の用語(「1つの定義規則」)は保証準拠するコンパイラーによって発生します。これにより、すべての変換単位に対して1つの定義のみが生成されます。この機能により、コーダーの作業が簡単になる場合があります。小さな関数の場合、その定義を.cppファイル(ゲッター、セッターなど)に入れたくない場合があります。
さらに、ヘッダーのみの構成であるtemplate関数の場合、この効果は必須です。したがって、template関数はデフォルトでinlineです。

例:

class A {
public:
  void setMember (int i) { m_i = i; }
};

この例では、ほとんどの場合、コンパイラで両方の用語で十分です。

class A {
  inline virtual ~A () = 0;
};
A::~A() {}

ここでは、コンパイラは2番目の要件のみで十分です。

11
iammilind

メソッド関数をインラインにする唯一の理由は、ヘッダーでメソッドを定義する場合です。

ヘッダーにメソッド関数を定義し、インラインキーワードを配置せず、ヘッダーを複数のヘッダーファイルまたはソースファイルに含めると、メソッドの複数の定義が得られます。

9.3/2メンバー関数[class.mfct]のc ++ 11標準は、次のように述べています。

メンバー関数は、そのクラス定義で定義(8.4)できます。その場合、それはインラインメンバー関数(7.1.2)です。

8
BЈовић

コンパイラは、inlineキーワードで指定されている場合、インライン化を無視できます。メソッドの実装がクラス定義内に存在する場合、それは別のことであり、無視することはできません。 (それは可能ですが、それはコンパイラを不適合にします)

設計の背後にある理由-inlineキーワードはそれを義務付けていないので、コンパイラに実際に関数をインライン化させることができるメカニズムが必要だったと思います。ただし、一般に、インラインメソッドの定義は、getterメソッドやsetterメソッド、またはいくつかの些細な2ライナーのような場合にのみ行われます。そしてテンプレートですが、それは別の問題です。

インライン化は、関数の定義がコンパイラーに表示される必要があるという点でヘッダーとソースファイルと関係があり、実際に呼び出しをインライン化する方法を認識します。ヘッダーで定義された関数よりも、実装ファイルで定義された関数をインライン化することは困難です。

編集:補足として、opが参照している段落は7.1.2.3です:

クラス定義内で定義された関数はインライン関数です[...]。

EDIT2:

どうやら、インライン関数とインライン置換の間にはいくつかの違いがあります。 1つは関数のプロパティであり、インライン置換だけが含まれているわけではありません。2つ目は、関数本体が実際に呼び出された場所に貼り付けられることを意味します。

そのため、関数をインライン化することはできますが、呼び出される代わりに本体を貼り付けることはできません。

2
Luchian Grigore

定義がクラス内にある場合、クラス定義は複数の変換ユニットから使用されるヘッダーファイルに存在すると想定されるため、inlineとして宣言されたかのように扱われます。したがって、非インライン定義はすべてここでは、単一定義規則に違反します。

コンパイラーは、明示的または暗黙的にインラインである関数がリンカーエラーを引き起こさないように注意する限り、いつものように、考えていることは何でも自由にインライン化できます。それがどのように行われるかは、言語仕様によって開かれたままです-関数のインライン化はもちろん機能しますが、シンボルの可視性を降格したり、シンボルの名前を翻訳ユニット固有の名前に変更したりすることもできます(関数が匿名の名前空間にあるかのように) )、または(それらのほとんどがそうであるように)その関数の複数のコピーが存在する可能性があり、それらの1つを除くすべてを破棄する必要があることをリンカーに通信します。

したがって、要するに、明示的にinlineと宣言されている関数と同じように扱われます。

2
Simon Richter

あなたが参照する2つのことは異なる側面であり、混同しないでください。

1)C++標準では、クラス定義内で定義されたすべてのメンバー関数はインラインであるとされています

2)コンパイラは関数のインライン化を無視できると聞きました

1)は、クラス宣言自体の内部でメンバー関数を定義する場合です。すなわち:ヘッダーファイル内。そのため、キーワードを指定する必要はありません(例:inline

2)inlineキーワードを明示的に使用することにより、関数をインラインとして指定できます。これは実際にはコンパイラへの要求です。コンパイラーは、最適化のいくつかの規則に従って、関数をインライン化する場合としない場合があります。

0