pit-rayの備忘録

知識のあうとぷっと

インライン関数はヘッダファイルで実装する

2020/3/6に更新しました

 インライン関数はC++やC99以降のC言語でサポートされています。このしくみは、マクロ関数のように呼び出しのオーバヘッドを避けることができます。

 関数におけるオーバーヘッドとは、関数の定義が別のアドレスにあることが原因で発生します。低レベルに言うと、CPUのプログラムカウンタが関数の呼び出し位置まで来ると、関数本体がおかれているメモリアドレスに飛んで戻ってくることが無駄な負荷をかけるわけです。それは、DLLなどの動的リンクの場合に顕著になります。

 このような関数のオーバーヘッドを避けるために、呼び出しを定義コードに置き換えてしまおうという考え方が、インライン関数の根本にあります。これは、プリプロセッサ命令としての#defineを用いたマクロ関数と似たような動作となります。

 しかしながら、#defineはコード的に置換してしまうため、コンパイラには関数として認知されません。加えて、マクロ関数は未定義の動作となる可能性があり、脆弱性の源となります。詳しくはPRE00-C. 関数形式マクロよりもインライン関数やスタティック関数を使う

 インライン関数を使うためには、コンパイラに、インライン化を要請する必要があります。それは、明示的なものと非明示的なものに大別されます。それぞれ次に示します。

//従来のマクロ
#define five() 5

//inlineキーワードを用いた明示的インライン要請
inline int five() {
    return  5 ;
}

//メンバ関数にすることによる非明示的インライン要請
class Hoge {
public:
    int five() const {
        return 5 ;
    }
} ;

//呼び出し方は変わらない
five() ;

このように、inlineキーワードを先頭につけるか、メンバ関数またはfriend関数とすることで、コンパイラにインライン要請をすることができます。

 ただし、先ほど述べたように、インライン関数は定義コードで置き換えるため、そのコードが長いとオブジェクトコードが肥大化します。したがって、複雑なコードであったり、莫大な関数などは、コンパイラによって拒否される可能性があります。また、変更した際にコンパイルし直す必要があり、バイナリのパッチを用いたアップデートができないというデメリットもあります。

 インライン化は、コンパイラによってオブジェクトコードの置換を行います。したがって、コンパイラは、コンパイル時に定義を知っている必要があります。コンパイルはソースファイル単位でオブジェクト化するので、単一の呼び出しを除き、インライン関数は、ヘッダファイルに定義を書き、#includeで定義をコンパイラに教えるのが望ましいと言えます。

 ただし、フレームワークによっては、リンク時にインライン化をするものもあるようですが、有名なコンパイラを使う上では、ヘッダファイルに実装するのが安全です。

参考文献
・Scott Meyers 著, 小林健一郎 訳『Effective C++ 第3版』2014