1. ホーム
  2. c++

[解決済み] インラインの使い道はまだあるのか?重複

2023-03-21 09:17:04

質問

私は信じていた。 inline は時代遅れだと信じていました。 ここで :

<ブロッククオート

関数をどのように指定しても inline として指定された関数を呼び出した場合、コンパイラはその関数を無視してもよいということになります。 inline .

しかし について は私が知らないことを理解しているようだ。で この質問 について、彼と私はかなり行き来しています。 inline がまだ有用であるかどうかについて、彼と私はかなり行き違います。

この質問は ではなく に関する質問です。

コンパイラは inline を自在に操ることができますので inline はそこでは役に立ちません。 どこで inline は強制的に使われます。 提案しない コンパイルされたコードに変更を加えるか?

どのように解決するのですか?

私の「秘密の理解」を、できる限り説明したいと思います。

ここには 2 つの全く別のコンセプトがあります。1 つは、関数本体を呼び出し場所で直接繰り返すことにより、関数呼び出しを置き換えるコンパイラの機能です。もうひとつは、複数の翻訳ユニットで関数を定義する可能性です (= 複数の .cpp ファイル) で関数を定義できることです。

1つ目は、関数のインライン化と呼ばれるものです。2つ目は目的の inline というキーワードを使うことです。 歴史的に inline と書かれた関数をインライン化するようにコンパイラに強く要求していました。 inline . コンパイラの最適化が進むにつれて、この機能は後退していき、現在では inline を関数のインライン化の提案として使うことは、本当に時代遅れです。コンパイラは、より良い最適化であると判断した場合、喜んでそれを無視し、完全に別のものをインライン化します。

私たちは明示的な inline -インライン化の関係については対処できたでしょうか。現在のコードには何もありません。

では、実際にどのような目的で inline キーワードは何のためにあるのでしょうか?それは簡単です。 inline とマークされた関数は、1 つの定義規則 (ODR) に違反することなく複数の翻訳ユニットで定義することができます。次の 2 つのファイルを想像してみてください。

ファイル1.cpp

int f() { return 42; }

int main()
{ return f(); }

ファイル2.cpp

int f() { return 42; }

このコマンドは

> gcc file1.cpp file2.cpp

リンカーエラーが発生し、シンボル f が2回定義されていると文句を言うリンカーエラーが発生します。

しかし、関数をマークする場合は inline キーワードでマークすると、コンパイラやリンカに具体的に次のように伝えます: "この関数の複数の同じ定義が ではなく エラーにならないようにしてください。

ということで、以下のようにするとうまくいきます。

ファイル1.cpp

inline int f() { return 42; }

int main()
{ return f(); }

ファイル2.cpp

inline int f() { return 42; }

この2つのファイルを一緒にコンパイル・リンクしても、リンカエラーは発生しません。

の定義は、もちろん f の定義は、ファイルにそのまま書かれている必要はないことに注意してください。それは #include d ヘッダーファイルから来ることができます。

f.hpp

inline int f() { return 42; }

ファイル1.cpp

#include "f.hpp"

int main()
{ return f(); }

ファイル2.cpp

#include "f.hpp"

基本的に、ヘッダファイルに関数の定義を書き込むためには、その関数を inline としてマークしなければならず、さもなければ多重定義エラーになります。


パズルの最後のピースは、なぜキーワードの実際のスペルが inline と綴られているのでしょうか?理由は簡単で、関数をインライン化する (つまり、呼び出し元で関数の本体を繰り返すことで呼び出しを置き換える) には、コンパイラは を持つ を持たなければなりません。

C++は別のコンパイルモデルに従っており、ここでは コンパイラ は現在生成しているファイル以外のオブジェクトファイルにアクセスすることができません。したがって、関数をインライン化するためには、その定義が現在の翻訳ユニットの一部でなければなりません。もし、複数の翻訳ユニットでインライン化したいのであれば、その定義はすべての翻訳ユニットに存在しなければなりません。通常、これは多重定義エラーにつながります。ですから、もしあなたが関数をヘッダに置いて #include としてマークしなければなりません。 inline としてマークし、多重定義エラーを防ぐ必要があります。

今日でも、コンパイラは適切と思われる関数をインライン化する一方で、その関数の定義にアクセスしなければならないことに注意してください。そのため inline キーワードはヒントとして要求されません。 を有効にする を有効にする必要があります。また、定義がないと、コンパイラーは単に関数をインライン化することができません。

コンパイラはできません。リンカーは可能です。最新の最適化技術には、リンクタイム コード生成 (別名、プログラム全体の最適化) があり、実際のリンクの前に、リンク プロセスの一部として、すべてのオブジェクト ファイルに対して最適化ツールが実行されます。このステップでは、もちろんすべての関数定義が利用可能であり、インライン化も完全に可能であり、1つも inline キーワードを使用することなくインライン化が可能です。しかし、この最適化は一般にビルド時間、特に大規模なプロジェクトではコストがかかります。このことを考慮すると、インライン化のために LTCG のみに依存することは最良の選択とは言えないかもしれません。


完全性のために。最初の部分で少しごまかしました。ODR プロパティは、実際には inline キーワードのプロパティではなく インライン関数 (これは言語の用語です)。インライン関数のルールは

  • リンカーエラーを起こすことなく、複数の翻訳ユニットで定義することができる
  • 使用されるすべての翻訳ユニットで定義する必要があります。
  • すべての定義はトークン対トークン、エンティティ対エンティティの同一でなければなりません。

inline キーワードは関数をインライン関数に変えます。関数をインライン化するもう一つの方法は、クラス定義の中で関数を直接定義する(単に宣言するのではない)ことです。そのような関数は、たとえ inline キーワードがなくても自動的にインライン化されます。