C++ のインライン関数
C++ には、関数呼び出しのオーバーヘッドを軽減するためのインライン関数が用意されています。インライン関数とは、呼び出すとインライン展開される関数です。インライン関数が呼び出されると、インライン関数のコード全体がインライン関数呼び出しの時点で挿入または置換されます。この置換は、コンパイル時に C++ コンパイラによって実行されます。インライン関数は小さい場合に効率が向上する可能性があります。
構文:
inline return-type function-name(parameters) { // function code }
インライン化はコンパイラに対する単なるリクエストであり、コマンドではないことに注意してください。コンパイラはインライン化の要求を無視できます。
コンパイラは、次のような状況ではインライン化を実行できない場合があります。
- 関数にループが含まれている場合。 ( for、while、do-while )
- 関数に静的変数が含まれている場合。
- 関数が再帰的である場合。
- 関数の戻り値の型が void 以外で、関数本体に return 文が存在しない場合。
- 関数に switch または goto ステートメントが含まれている場合。
インライン関数が使用される理由
プログラムが関数呼び出し命令を実行すると、CPU は関数呼び出しに続く命令のメモリ アドレスを保存し、関数の引数をスタックにコピーし、最後に指定された関数に制御を渡します。次に、CPU は関数コードを実行し、関数の戻り値を事前定義されたメモリ位置/レジスタに格納し、呼び出し関数に制御を返します。関数の実行時間が、呼び出し側関数から呼び出される関数 (呼び出し先) への切り替え時間よりも短い場合、これはオーバーヘッドになる可能性があります。
大規模な関数や複雑なタスクを実行する関数の場合、関数呼び出しのオーバーヘッドは、関数の実行にかかる時間に比べれば通常は重要ではありません。ただし、一般的に使用される小さな関数の場合、関数呼び出しに必要な時間は、実際に関数のコードを実行するのに必要な時間よりもはるかに長いことがよくあります。小規模な関数の実行時間は切り替え時間よりも短いため、このオーバーヘッドは小規模な関数で発生します。
インライン関数の利点:
- 関数呼び出しのオーバーヘッドは発生しません。
- また、関数の呼び出し時にスタック上のプッシュ/ポップ変数のオーバーヘッドも節約されます。
- また、関数からの戻り呼び出しのオーバーヘッドも節約されます。
- 関数をインライン化する場合、コンパイラーが関数の本体に対してコンテキスト固有の最適化を実行できるようにすることができます。このような最適化は、通常の関数呼び出しでは不可能です。その他の最適化は、呼び出し側コンテキストと呼び出されるコンテキストのフローを考慮することで実現できます。
- インライン関数は、プリアンブルおよびリターンと呼ばれる関数よりも少ないコードを生成できるため、組み込みシステムでは (小規模であれば) 便利な場合があります。
インライン関数の欠点:
- インライン化関数から追加された変数は追加のレジスタを消費します。インライン化関数の後、レジスタを使用する変数の数が増加すると、レジスタ変数のリソース使用率にオーバーヘッドが発生する可能性があります。これは、インライン関数本体が関数呼び出しの時点で置換されると、関数で使用される変数の合計数も挿入されることを意味します。したがって、変数に使用されるレジスタの数も増加します。したがって、関数のインライン化後に変数の数が大幅に増加すると、レジスタ使用率にオーバーヘッドが確実に発生します。
- 使用するインライン関数が多すぎると、同じコードが重複するため、バイナリ実行可能ファイルのサイズが大きくなります。
- インライン化が多すぎると、命令キャッシュのヒット率も低下する可能性があり、その結果、キャッシュ メモリからプライマリ メモリへの命令フェッチの速度が低下します。
- インライン関数は、誰かがインライン関数内のコードを変更した場合、コンパイル時間のオーバーヘッドが増加する可能性があります。その場合、コンパイラは変更を反映するためにすべてのコードをもう一度置き換える必要があるため、すべての呼び出し位置を再コンパイルする必要があります。そうしないと、古いままになります。機能性。
- インライン関数は、多くの組み込みシステムでは役に立たない可能性があります。組み込みシステムでは速度よりもコード サイズの方が重要だからです。
- インライン関数はバイナリ実行可能ファイルのサイズを増大させる可能性があるため、インライン関数によってスラッシングが発生する可能性があります。メモリ内でスラッシングが発生すると、コンピュータのパフォーマンスが低下します。次のプログラムは、inline 関数の使用法を示しています。
例:
C++
#include> using> namespace> std;> inline> int> cube(> int> s) {> return> s * s * s; }> int> main()> {> > cout < <> 'The cube of 3 is: '> < < cube(3) < <> '
'> ;> > return> 0;> }> |
出力
The cube of 3 is: 27
インライン関数とクラス
クラス内にインライン関数を定義することも可能です。実際、クラス内で定義されたすべての関数は暗黙的にインライン化されます。したがって、インライン関数のすべての制限がここにも適用されます。クラス内でインライン関数を明示的に宣言する必要がある場合は、クラス内で関数を宣言し、 inline キーワードを使用してクラスの外で関数を定義します。
構文:
class S { public: inline int square(int s) // redundant use of inline { // this function is automatically inline // function body } }; 上記のスタイルは、悪いプログラミング スタイルとみなされます。最良のプログラミング スタイルは、クラス内に関数のプロトタイプを記述し、それを関数定義でインラインとして指定することです。
例えば:
class S { public: int square(int s); // declare the function }; inline int S::square(int s) // use inline prefix { } 例:
C++
// C++ Program to demonstrate inline functions and classes> #include> using> namespace> std;> class> operation {> > int> a, b, add, sub, mul;> > float> div> ;> public> :> > void> get();> > void> sum();> > void> difference();> > void> product();> > void> division();> };> inline> void> operation ::get()> {> > cout < <> 'Enter first value:'> ;> > cin>> ;>> > cout < <> 'Enter second value:'> ;> > cin>> b;>> }> inline> void> operation ::sum()> {> > add = a + b;> > cout < <> 'Addition of two numbers: '> < < a + b < <> '
'> ;> }> inline> void> operation ::difference()> {> > sub = a - b;> > cout < <> 'Difference of two numbers: '> < < a - b < <> '
'> ;> }> inline> void> operation ::product()> {> > mul = a * b;> > cout < <> 'Product of two numbers: '> < < a * b < <> '
'> ;> }> inline> void> operation ::division()> {> > div> = a / b;> > cout < <> 'Division of two numbers: '> < < a / b < <> '
'> ;> }> int> main()> {> > cout < <> 'Program using inline function
'> ;> > operation s;> > s.get();> > s.sum();> > s.difference();> > s.product();> > s.division();> > return> 0;> }> |
出力:
Enter first value: 45 Enter second value: 15 Addition of two numbers: 60 Difference of two numbers: 30 Product of two numbers: 675 Division of two numbers: 3
マクロの何が問題なのでしょうか?
C 言語に精通している読者は、C 言語がマクロを使用することを知っています。プリプロセッサは、マクロ コード内のすべてのマクロ呼び出しを直接置き換えます。マクロではなく、常にインライン関数を使用することをお勧めします。 Bjarne Stroustrup 博士によると、C++ マクロの作成者は C++ ではほとんど必要なく、エラーが発生しやすいとのことです。 C++ でのマクロの使用にはいくつかの問題があります。マクロはクラスのプライベート メンバーにアクセスできません。マクロは関数呼び出しのように見えますが、実際は違います。
例:
C++
// C++ Program to demonstrate working of macro> #include> using> namespace> std;> class> S {> > int> m;> public> :> > // error> #define MAC(S::m)> };> |
出力:
Error: '::' may not appear in macro parameter list #define MAC(S::m)
C++ コンパイラはインライン関数の引数の型をチェックし、必要な変換が正しく実行されます。プリプロセッサ マクロではこれを行うことができません。もう 1 つは、マクロはプリプロセッサによって管理され、インライン関数は C++ コンパイラによって管理されることです。注意: クラス内で定義されているすべての関数は暗黙的にインラインであり、C++ コンパイラはこれらの関数のインライン呼び出しを実行しますが、関数が仮想の場合、C++ コンパイラはインライン実行できません。その理由は、仮想関数が呼び出されて、コンパイル時ではなく実行時に解決されるためです。仮想とは実行時まで待機することを意味し、インラインとはコンパイル中を意味します。コンパイラーがどの関数が呼び出されるのかわからない場合、どのようにインライン化を実行できるのでしょうか?もう 1 つ覚えておくべき点は、関数呼び出し中に費やされる時間が関数本体の実行時間と比較して長い場合にのみ、関数をインライン化することが有益であるということです。
インライン関数がまったく効果を持たない例:
inline void show() { cout < < 'value of S = ' < < S < < endl; } 上記の関数は実行に比較的時間がかかります。一般に、入出力 (I/O) 操作を実行する関数は、かなりの時間がかかるため、インラインとして定義すべきではありません。 I/O ステートメントにかかる時間は関数呼び出しのオーバーヘッドをはるかに超えるため、技術的には show() 関数のインライン化の価値は限られています。使用しているコンパイラによっては、関数がインライン展開されていない場合に警告が表示される場合があります。
Java や C# などのプログラミング言語はインライン関数をサポートしていません。しかし、Java では、final メソッドはサブクラスによってオーバーライドできず、final メソッドの呼び出しはコンパイル時に解決されるため、小さな Final メソッドが呼び出されたときにコンパイラはインライン化を実行できます。
C# では、JIT コンパイラは、小さな関数呼び出しをインライン化することによってコードを最適化することもできます (ループ内で呼び出されたときに小さな関数の本体を置き換えるなど)。最後に留意すべきことは、インライン関数は C++ の貴重な機能であるということです。インライン関数を適切に使用するとパフォーマンスが向上しますが、インライン関数が恣意的に使用された場合、より良い結果は得られません。言い換えれば、プログラムのパフォーマンスの向上を期待しないでください。すべての関数をインライン化しないでください。インライン関数はできるだけ小さくすることをお勧めします。