ヘッダーにC ++インライン関数があるのはなぜですか?


120

注:これは、インライン関数の使用方法や機能についての問題ではなく、なぜそれらが現在の方法で行われるのかということです。

クラスメンバー関数の宣言では、関数をとして定義する必要はありませんinline。関数の実際の実装のみです。たとえば、ヘッダーファイルでは次のようになります。

struct foo{
    void bar(); // no need to define this as inline
}

なぜクラスのインライン実装では機能しない持っているヘッダファイルにしますか?インライン関数を.cppファイルに配置できないのはなぜですか?インライン定義を.cppファイルに入れようとすると、次の行に沿ってエラーが発生します。

error LNK2019: unresolved external symbol 
"public: void __thiscall foo::bar(void)"
(?bar@foo@@QAEXXZ) referenced in function _main 
1>C:\Users\Me\Documents\Visual Studio 2012\Projects\inline\Debug\inline.exe 
: fatal error LNK1120: 1 unresolved externals



@Charles私は2番目のリンクは似ていると思いますが、インラインがそのように機能する理由の背後にあるロジックについてもっと質問しています。
thecoshman

2
その場合、「インライン」または「ヘッダーファイル」のどちらかを誤解していると思います。あなたの主張はどちらも真実ではありません。メンバー関数をインラインで実装し、ヘッダーファイルにインライン関数の定義を含めることができます。質問を明確にできますか?
CBベイリー

ポスト編集は、私はあなたが状況について質問することができると思うinlineの定義に表示されなく対事前申告その逆。もしそうなら、これが役立つことがあります。stackoverflow.com/questions/4924912/...
CBベイリー

回答:


122

inline関数の定義はヘッダーファイルにある必要はありませんが、インライン関数の1つの定義規則(ODR)のため、関数の同一の定義がそれを使用するすべての変換単位に存在する必要があります。

これを実現する最も簡単な方法は、ヘッダーファイルに定義を配置することです。

関数の定義を単一のソースファイルに配置する場合は、宣言しないでくださいinline。宣言されてinlineいない関数は、コンパイラーが関数をインライン化できないことを意味しません。

関数を宣言する必要があるかどうかinlineは、通常、1つの定義ルールのどのバージョンに基づいて行うべきかを選択することです。追加inlineし、その後の制約によって制限されることはほとんど意味がありません。


しかし、コンパイラは.hppファイルを含む.cppファイルをコンパイルしないので、.cppファイルをコンパイルすると、減速とソースファイルの両方が含まれます。取り込まれた他のヘッダーファイルは単なるヘッダーファイルなので、コンパイラーはそれらの関数が存在し、他のソースファイルに実装されることを「信頼」できます。
取り込ま

1
これは実際、+1私よりもはるかに良い答えです。
sbi

2
@thecoshman:2つの違いがあります。ソースファイルとヘッダーファイル。慣例により、ヘッダーファイルは通常、翻訳単位の基礎ではないソースファイルを参照しますが、他のソースファイルからのみ#includeされます。次に、宣言と定義があります。ヘッダーファイルまたは「通常の」ソースファイルのいずれかに関数の宣言または定義を含めることができます。コメントで何を求めているのかわかりません。
CBベイリー

心配しないでください、なぜ今なのかわかります。あなたと@Xanatosの答えの組み合わせが私にそれを説明しました。
thecoshman

113

それを見るには2つの方法があります:

  1. インライン関数はヘッダーで定義されています。これは、関数呼び出しをインライン化するために、コンパイラーが関数本体を認識できる必要があるためです。素朴なコンパイラーがそれを行うには、関数本体が呼び出しと同じ変換単位内になければなりません。(最近のコンパイラーは翻訳単位全体で最適化できるため、関数定義が別の翻訳単位にある場合でも関数呼び出しはインライン化される可能性がありますが、これらの最適化は高価であり、常に有効であるとは限らず、常にサポートされていませんでした。コンパイラ)

  2. ヘッダーで定義された関数にはマークを付ける必要がありますinline。そうしないと、ヘッダーを含むすべての変換単位に関数の定義が含まれ、リンカーは複数の定義について不平を言う(1つの定義ルールの違反)。inlineキーワードが複数の翻訳単位が(同一)の定義を格納することができ、これを抑制することができます。

2つの説明は、inlineキーワードが期待どおりに機能しないという事実に要約されます。

C ++コンパイラーは、プログラムの監視可能な動作を変更しない限り、好きなときにいつでもインライン最適化(関数呼び出しを呼び出された関数の本体で置き換え、呼び出しオーバーヘッドを節約)を自由に適用できます。

inlineキーワードは、それを作るより簡単に関数定義は、複数の翻訳単位で表示できるようにすることが、コンパイラを意味するものではありませんキーワードを使用することにより、この最適化を適用するコンパイラの機能をインライン化すると、ないないキーワードを使用してコンパイラによる関数のインライン展開を禁止します。


23

これはC ++コンパイラの制限です。ヘッダーに関数を配置すると、インライン化できるすべてのcppファイルで関数の「ソース」を確認でき、コンパイラーによってインライン化を実行できます。それ以外の場合、インライン化はリンカーによって行われる必要があります(各cppファイルはobjファイルに個別にコンパイルされます)。問題は、リンカで実行するのがはるかに難しいことです。「テンプレート」クラス/関数にも同様の問題があります。リンカはそれらをインスタンス化する(特殊なバージョンを作成する)問題があるため、コンパイラによってインスタンス化する必要があります。一部の新しいコンパイラ/リンカは、コンパイラが最初のパスを実行する「2パス」コンパイル/リンクを実行できます。その後、リンカが作業を行い、未解決の問題を解決するためにコンパイラを呼び出します(インライン/テンプレート...)


ああなるほど!はい、クラス自体ではなく、インライン関数を使用します。インライン関数を使用するその他のコードです。インライン化されているクラスのヘッダーファイルのみが表示されます。
thecoshman

11
私はこの回答に同意しません。これはC ++コンパイラの制限ではありません。それは純粋に言語規則が指定される方法です。言語規則は単純なコンパイルモデルを許可しますが、代替の実装を禁止しません。
CBベイリー

3
@Charlesに同意します。実際、翻訳単位をまたがって関数をインライン化するコンパイラーがあるため、これは間違いなくコンパイラーの制限によるものではありません。
sbi

5
この回答にはいくつかの技術的なエラーがあるようですが、コンパイラがヘッダーファイルなどを処理する方法を確認するのに役立ちました。
thecoshman、2011

10

その理由は、コンパイラが呼び出しの代わりに定義をドロップできるようにするために、実際に定義を確認する必要があるためです。

CとC ++は非常に単純なコンパイルモデルを使用することに注意してください。この場合、コンパイラは常に一度に1つの変換単位しか認識しません。(これはエクスポートに失敗します。これが、1つのベンダーのみが実際に実装した主な理由です。)


9

c ++ inlineキーワードは誤解を招くものであり、「この関数をインライン化する」という意味ではありません。関数がインラインとして定義されている場合は、すべての定義が等しい限り、関数を複数回定義できることを意味します。inline呼び出された時点でコードをインライン化する代わりに、呼び出された実際の関数としてマークされた関数が完全に合法です。

テンプレートにはヘッダーファイルで関数を定義する必要があります。たとえば、テンプレートクラスは実際にはクラスではないため、 ため、複数のバリエーションを作成できるクラスのテンプレートです。たとえば、Fooテンプレートを使用してFooクラスを作成するときにコンパイラーがFoo<int>::bar()関数を作成Foo<T>::bar()できるようにするには、の実際の定義が可視である必要があります。


また、クラスのテンプレートであるためテンプレートクラスではなく、クラステンプレートと呼ばれます。
sbi

4
最初の段落は完全に正しいです(そして、「誤解を招く」ことを強調できたらいいのですが)が、テンプレートに非等分性の必要性はわかりません。
Thomas Edleson、2011

一部のコンパイラーは、関数をおそらくインライン化できるというヒントとしてそれを使用しますが、実際には、inline宣言しinlineたからといってインライン化されるとは限りません(インライン化されないことも宣言されていません)。
キースM

4

私はこれが古いスレッドであることを知っていますが、そのexternキーワードについて言及する必要があると思いました。私は最近この問題に遭遇し、次のように解決しました

Helper.h

namespace DX
{
    extern inline void ThrowIfFailed(HRESULT hr);
}

Helper.cpp

namespace DX
{
    inline void ThrowIfFailed(HRESULT hr)
    {
        if (FAILED(hr))
        {
            std::stringstream ss;
            ss << "#" << hr;
            throw std::exception(ss.str().c_str());
        }
    }
}

6
これは、プログラム全体の最適化(WPO)を使用していない限り、通常、実際に関数がインライン化されることはありません。
チャックウォルボーン2014

3

なぜなら、コンパイラーはそれらをインライン化するためにそれらを見る必要があるからです。ヘッダーファイルは、他の翻訳単位に一般的に含まれる「コンポーネント」です。

#include "file.h"
// Ok, now me (the compiler) can see the definition of that inline function. 
// So I'm able to replace calls for the actual implementation.

1

インライン関数

C ++では、マクロはインライン関数にすぎません。SOマクロはコンパイラーの制御下にあります。

  • 重要:クラス内で関数を定義すると、自動的にインラインになります

インライン関数のコードは、呼び出された場所で置き換えられるため、関数呼び出しのオーバーヘッドが軽減されます。

場合によっては、関数のインライン化が機能しないことがあります。

  • インライン関数内で静的変数が使用されている場合。

  • 機能が複雑な場合。

  • 関数の再帰呼び出しの場合

  • 暗黙的または明示的にとられた関数のアドレス

以下のようにクラス外で定義された関数はインラインになる可能性があります

inline int AddTwoVar(int x,int y); //This may not become inline 

inline int AddTwoVar(int x,int y) { return x + y; } // This becomes inline

クラス内で定義された関数もインラインになります

// Inline SpeedMeter functions
class SpeedMeter
{
    int speed;
    public:
    int getSpeed() const { return speed; }
    void setSpeed(int varSpeed) { speed = varSpeed; }
};
int main()
{
    SpeedMeter objSM;
    objSM.setSpeed(80);
    int speedValue = A.getSpeed();
} 

ここでgetSpeed関数とsetSpeed関数の両方がインラインになります


ええと、おそらくいくつかの素晴らしい情報ですが、実際には理由を説明しようとはしていません。多分あなたはそうしますが、それを明確にしていないだけです。
thecoshman

2
次のステートメントは当てはまりません。「重要:クラス内で関数を定義すると、関数は自動的にインラインになります」宣言/定義に「インライン」と書いても、実際にインライン化されているとは限りません。テンプレートでもありません。多分あなたはコンパイラが自動的に「インライン」キーワードを想定することを意味しましたが、従う必要はありません、そして私が気付いたことはほとんどの場合それがそのようなヘッダー定義をインライン化しないことです。基本的な算術。
パブロアリエル

コメントありがとうございます...以下は、C ++のThinkingからの行ですmicc.unifi.it/bertini/download/programmazione/…400 ページ..確認してください..同意する場合は投票してください 感謝.....クラス内のインラインインライン関数を定義するには、通常、関数定義の前にinlineキーワードを付ける必要があります。ただし、これはクラス定義内では必要ありません。クラス定義内で定義する関数は、自動的にインラインになります。
Saurabh Raoot

彼らはコードではなく本を書くので、その本の著者は彼らが望むものを主張することができます。インラインコードをできるだけ避けて、ポータブル3Dデモを64KB未満に収めるために、これは深く分析する必要がありました。プログラミングは事実に関するものであり、宗教に関するものではないため、「神のプログラマー」が本の中でそれを実際に何が起こっているのかを表していない場合でも、本でそれを言っても問題はありません。そして、ほとんどのC ++の本には悪いアドバイスのコレクションがあり、その中には、レパートリーに追加するための巧妙なトリックを時々見つけることができます。
パブロアリエル

こんにちは@PabloArielありがとう...分析して私に知らせてください..私は分析に従ってこの回答を更新してもかまいません
Saurabh Raoot
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.