次の方法で共有


インライン関数 (C++)

inline キーワードは、コンパイラが関数の各呼び出しの代わりに、関数定義内のコードを置き換えることを示しています。

理論上は、インライン関数を使用すると、関数呼び出しに関連するオーバーヘッドが回避されるため、プログラムを高速化できます。 関数を呼び出すには、スタック上のリターン アドレスをプッシュし、引数をスタックにプッシュし、関数本体にジャンプしてから、関数が終了したときに戻り命令を実行する必要があります。 関数をインライン化することで、このプロセスがなくなります。 また、コンパイラには、インライン展開された関数と展開されていない関数を最適化するさまざまな機会があります。 インライン関数のトレードオフは、プログラムの全体的なサイズが増加する可能性があることです。

インライン コードを置き換えるかどうかは、コンパイラの判断で行われます。 たとえば、関数のアドレスが取得されている場合、または関数が大きすぎるとコンパイラが判断する場合、関数はインライン展開されません。

クラス宣言の本体で定義される関数は、暗黙的にインライン関数です。

次のクラス宣言では、Account コンストラクターはクラス宣言の本体で定義されているため、インライン関数です。 メンバー関数 GetBalanceDeposit、および Withdraw は、定義で inline に指定されます。 inline キーワードは、クラス宣言の関数宣言では省略可能です。

// account.h
class Account
{
public:
    Account(double initial_balance)
    {
        balance = initial_balance;
    }

    double GetBalance() const;
    double Deposit(double amount);
    double Withdraw(double amount);

private:
    double balance;
};

inline double Account::GetBalance() const
{
    return balance;
}

inline double Account::Deposit(double amount)
{
    balance += amount;
    return balance;
}

inline double Account::Withdraw(double amount)
{
    balance -= amount;
    return balance;
}

Note

クラス宣言では、関数は inline キーワードなしで宣言されていました。 inline キーワードをクラス宣言で指定できますが、結果は同じです。

特定のインライン メンバー関数は、すべてのコンパイル単位で同じ方法で宣言する必要があります。 インライン関数の定義は 1 つだけでなければなりません。

クラス メンバー関数は、その関数の定義に inline 指定子が含まれていない限り、既定で外部リンケージに設定されます。 前の例は、inline 指定子を使用してこれらの関数を明示的に宣言する必要がないことを示しています。 関数定義で inline を使用すると、インライン関数として扱われることがコンパイラに示されます。 ただし、関数を呼び出した後にその関数を inline として再宣言することはできません。

inline__inline__forceinline

inline 指定子と __inline 指定子は、関数本体のコピーを関数の各呼び出し位置に挿入するようコンパイラに示します。

挿入 ("インライン展開" または "インライニング" と呼ばれます) は、コンパイラ独自の費用対効果分析によってそれだけの価値があることがわかった場合にのみ行われます。 インライン展開では、関数呼び出しのオーバーヘッドが最小になりますが、コード サイズが大きくなるという潜在的なコストが発生します。

__forceinline キーワードは費用対効果分析をオーバーライドし、代わりにプログラマの判断に委ねられます。 __forceinline を使用する場合は注意が必要です。 __forceinline を無差別に使用すると、パフォーマンスがわずかに向上するだけで、コードが大きくなる可能性があります。場合によっては、パフォーマンスが低下することさえあります (たとえば、実行可能ファイルが大きくなってページングが増えるため)。

インライン展開に関するオプションとキーワードは、インライン展開の対象となる候補をコンパイラに示すだけです。 関数がインライン展開される保証はありません。 __forceinline キーワードを指定しても、特定の関数をコンパイラに強制的にインライン展開させることはできません。 /clr を指定してコンパイルする場合、関数にセキュリティ属性が適用されていると、コンパイラは関数をインライン展開しません。

以前のバージョンとの互換性を確保するため、_inline_forceinline は、コンパイラ オプション /Za (言語拡張機能の無効化) が指定されていない限り、それぞれ __inline__forceinline の同意語です。

inline キーワードは、インライン展開を優先することをコンパイラに指示します。 ただし、コンパイラはこれを無視できます。 このような動作になるのは、次の 2 つの場合です。

  • 再帰関数。
  • 翻訳単位の別の場所にあるポインターを通じて参照される関数。

これらの理由は、他の要因と共に、インライン展開に関するコンパイラの判断に影響する可能性があります。 関数のインライン展開を inline 指定子で確実に行うことはできません。

コンパイラは、ヘッダー ファイルで定義されているインライン関数を展開するのではなく、複数の翻訳単位内で呼び出し可能な関数として作成できます。 コンパイラは、単一定義規則 (ODR) 違反を防止するために、リンカーに対して生成された関数をマークします。

通常の関数と同様に、インライン関数での引数の評価に対して定義された順序はありません。 実際には、通常の関数呼び出しプロトコルを使用して渡された場合の引数の評価順序とは異なる可能性があります。

インライン関数展開が実際に行われるかどうかに影響を与えるには、/Ob コンパイラ最適化オプションを使用します。
/LTCG は、ソース コードで要求されているかどうかにかかわらず、モジュール間のインライン展開を行います。

例 1

// inline_keyword1.cpp
// compile with: /c
inline int max(int a, int b)
{
    return a < b ? b : a;
}

クラスのメンバー関数は、inline キーワードを使用するか、クラス定義内に関数定義を配置することにより、インラインとして宣言できます。

例 2

// inline_keyword2.cpp
// compile with: /EHsc /c
#include <iostream>

class MyClass
{
public:
    void print() { std::cout << i; }   // Implicitly inline

private:
    int i;
};

Microsoft 固有の仕様

__inline キーワードは inline に相当します。

次の場合、__forceinline を使用しても、コンパイラは関数をインライン展開できません。

  • /Ob0 (デバッグ ビルドの既定オプション) を指定して関数またはその呼び出し元がコンパイルされる。
  • 関数と呼び出し元が、異なる種類の例外処理を使用する (一方は C++ 例外処理、他方は構造化例外処理)。
  • 関数に可変個引数リストが含まれている。
  • /Ox/O1、または /O2 を指定してコンパイルされない限り、関数はインライン アセンブリを使用する。
  • 関数が再帰的であり、#pragma inline_recursion(on) が設定されていない。 プラグマによって、再帰関数はインライン展開され、既定の深さは 16 呼び出しになります。 インライン展開の深さを減らすには、inline_depth プラグマを使用します。
  • 関数が仮想で、仮想的に呼び出される。 仮想関数への直接呼び出しはインライン展開できます。
  • プログラムが関数のアドレスを受け取り、関数へのポインターによって呼び出される。 受け取られるアドレスを持っている関数への直接呼び出しはインライン展開できます。
  • 関数に naked __declspec 修飾子も付けられている。

__forceinline を使用して宣言された関数をコンパイラがインライン展開できない場合、次の状況でない限り、レベル 1 の警告が生成されます。

  • /Od または /Ob0 を使用して関数がコンパイルされる。 次のような場合、インライン展開が行われることはありません。
  • 関数が外部 (組み込みライブラリ内または別の翻訳単位内) で定義されている、あるいは仮想呼び出しターゲットまたは間接呼び出しターゲットである。 現在の翻訳単位内で見つからない非インライン展開コードをコンパイラが特定できない。

再帰関数は、inline_depth プラグマで指定された深さ (最大 16 の呼び出し) までインライン コードに置き換えることができます。 その深さの後、再帰関数の呼び出しは、関数のインスタンスへの呼び出しとして扱われます。 再帰関数がインライン ヒューリスティックによってチェックされる深さは、16 を超えることはできません。 inline_recursion プラグマは、現在展開中の関数のインライン展開を制御します。 関連情報については、インライン関数の展開 (/Ob) コンパイラ オプションの説明を参照してください。

Microsoft 固有の仕様はここまで

inline 指定子の使い方の詳細については、以下を参照してください。

インライン関数を使用する状況

インライン関数は、データ メンバーにアクセスできるようにする関数のような小さな関数に使用するのが最適です。 短い関数は、関数呼び出しのオーバーヘッドに影響を受けます。 長い関数は、呼び出すシーケンスと返すシーケンスにかかる時間が相対的に短く、インライン展開の利点が小さくなります。

Point クラスは次のように定義できます。

// when_to_use_inline_functions.cpp
// compile with: /c
class Point
{
public:
    // Define "accessor" functions
    // as reference types.
    unsigned& x();
    unsigned& y();

private:
    unsigned _x;
    unsigned _y;
};

inline unsigned& Point::x()
{
    return _x;
}

inline unsigned& Point::y()
{
    return _y;
}

このようなクラスの利用時は座標の操作は比較的一般的な操作であると仮定すれば、2 つのアクセサー関数 (前の例の xy) を inline と指定すると、通常、次のオーバーヘッドを削減できます。

  • 関数呼び出し (パラメーターの引き渡しおよびスタックへのオブジェクトのアドレスの配置を含む)
  • 呼び出し元のスタック フレームの保持
  • 新しいスタック フレームのセットアップ
  • 戻り値のやり取り
  • 古いスタックフレームの復元
  • Return

インライン関数とマクロ

マクロには、inline 関数との共通点がいくつかあります。 ただし、重要な違いがあります。 次の例を確認してください。

#include <iostream>

#define mult1(a, b) a * b
#define mult2(a, b) (a) * (b)
#define mult3(a, b) ((a) * (b))

inline int multiply(int a, int b)
{
    return a * b;
}

int main()
{
    std::cout << (48 / mult1(2 + 2, 3 + 3)) << std::endl; // outputs 33
    std::cout << (48 / mult2(2 + 2, 3 + 3)) << std::endl; // outputs 72
    std::cout << (48 / mult3(2 + 2, 3 + 3)) << std::endl; // outputs 2
    std::cout << (48 / multiply(2 + 2, 3 + 3)) << std::endl; // outputs 2

    std::cout << mult3(2, 2.2) << std::endl; // no warning
    std::cout << multiply(2, 2.2); // Warning C4244	'argument': conversion from 'double' to 'int', possible loss of data
}
33
72
2
2
4.4
4

マクロとインライン関数の違いを次に示します。

  • マクロは常にインラインで展開されます。 しかし、インライン関数がインライン化されるのは、コンパイラがそれが最適であると判断する場合のみです。
  • マクロによって予期しない動作が生じて、特定しにくいバグが発生する可能性があります。 たとえば、式 mult1(2 + 2, 3 + 3)2 + 2 * 3 + 3 に展開され、11 に評価されますが、予想される結果は 24 です。 一見有効な修正は、関数マクロの両方の引数を囲むかっこを追加することです。その結果、#define mult2(a, b) (a) * (b) になります。これにより、当面の問題は解決されますが、より大きい式の一部である場合に予期しない動作が引き続き発生する可能性があります。 これは前の例で示されており、#define mult3(a, b) ((a) * (b)) としてマクロを定義することで問題に対処できました。
  • インライン関数はコンパイラによるセマンティック処理の対象になりますが、プリプロセッサは同じメリットなしにマクロを展開します。 マクロはタイプ セーフではありませんが、関数はタイプ セーフです。
  • インライン関数に引数として渡された式は、1 回だけ評価されます。 マクロでは、引数として渡された式が複数回評価される可能性があります。 たとえば、次のことを考慮してください。
#include <iostream>

#define sqr(a) ((a) * (a))

int increment(int& number)
{
    return number++;
}

inline int square(int a)
{
    return a * a;
}

int main()
{
    int c = 5;
    std::cout << sqr(increment(c)) << std::endl; // outputs 30
    std::cout << c << std::endl; // outputs 7

    c = 5;
    std::cout << square(increment(c)) << std::endl; // outputs 25
    std::cout << c; // outputs 6
}
30
7
25
6

この例では、式 sqr(increment(c))((increment(c)) * (increment(c))) に展開されるので、関数 increment が 2 回呼び出されます。 これにより、increment の 2 回目の呼び出しで 6 が返されるため、式は 30 に評価されます。 副作用を含む式は、マクロで使用するときに結果に影響する可能性があります。完全に展開されたマクロを調べて、動作が意図されたものであるかどうかを確認します。 代わりに、インライン関数 square が使用された場合、関数 increment は 1 回だけ呼び出され、正しい結果の 25 が取得されます。

関連項目

noinline
auto_inline