関数/メソッドのキーワードinline
をC++でいつ書く必要がありますか?
いくつかの回答を見た後、いくつかの関連する質問:
C ++の関数/メソッドのキーワード「インライン」を記述すべきでないのはいつですか?
コンパイラはいつ関数/メソッドを「インライン」にするかわからないのですか?
関数/メソッドの「インライン」を書き込むときに、アプリケーションがマルチスレッドであるかどうかは重要ですか?
関数/メソッドのキーワードinline
をC++でいつ書く必要がありますか?
いくつかの回答を見た後、いくつかの関連する質問:
C ++の関数/メソッドのキーワード「インライン」を記述すべきでないのはいつですか?
コンパイラはいつ関数/メソッドを「インライン」にするかわからないのですか?
関数/メソッドの「インライン」を書き込むときに、アプリケーションがマルチスレッドであるかどうかは重要ですか?
ああ、私のペットのおしっこの1つ。
inline
これは、コンパイラに関数をインライン化するように指示するディレクティブに static
似ています。、、はリンケージディレクティブであり、コンパイラではなく、リンカによってほぼ排他的に使用されます。extern
extern
static
inline
関数をインライン化する必要があるとコンパイラーに示唆すると言われてinline
います。それは1998年には真実だったかもしれませんが、10年後、コンパイラーはそのようなヒントを必要としません。言うまでもなく、コードの最適化に関しては通常、人間は間違っているため、ほとんどのコンパイラは「ヒント」を無視します。
static
-変数/関数名を他の変換単位で使用することはできません。リンカは、別の変換ユニットから静的に定義された変数/関数を誤って使用しないようにする必要があります。
extern
-この変換ユニットでこの変数/関数名を使用しますが、定義されていなくても文句を言わないでください。リンカはそれを分類し、externシンボルを使用しようとしたすべてのコードがそのアドレスを持っていることを確認します。
inline
-この関数は複数の変換単位で定義されます。心配する必要はありません。リンカは、すべての変換ユニットが変数/関数の単一のインスタンスを使用することを確認する必要があります。
注:一般に、テンプレートの宣言は、すでにinline
のリンケージセマンティクスを持っているため、無意味です。inline
ただし、テンプレートの明示的な特殊化とインスタンス化を使用する必要inline
があります。
あなたの質問に対する具体的な答え:
C ++の関数/メソッドのキーワード「インライン」はいつ書く必要がありますか?
関数をヘッダーで定義する場合のみ。より正確には、関数の定義が複数の変換単位で表示される場合のみです。ヘッダーファイルで小さな(1つのライナーのように)関数を定義することをお勧めします。これにより、コードを最適化する際に使用する情報がコンパイラーに提供されます。また、コンパイル時間が長くなります。
C ++の関数/メソッドのキーワード「インライン」を記述すべきでないのはいつですか?
コンパイラーがインライン化するとコードの実行速度が上がると思うからといって、インライン化を追加しないでください。
コンパイラはいつ関数/メソッドを「インライン」にするかわからないのですか?
一般的に、コンパイラーはこれをあなたよりもうまく行うことができます。ただし、関数定義がない場合、コンパイラにはコードをインライン化するオプションがありません。最大限に最適化されたコードでは、通常、private
要求するかどうかに関係なく、すべてのメソッドがインライン化されます。
GCCでのインライン化を防ぐために、を使用し__attribute__(( noinline ))
、VisualStudioではを使用します__declspec(noinline)
。
関数/メソッドの「インライン」を書き込むときに、アプリケーションがマルチスレッドであるかどうかは重要ですか?
マルチスレッドは、インライン化にはまったく影響しません。
残りの誤解を分散させるための説得力のある例を使用して、このスレッドのすべての優れた回答に貢献したいと思います。
次のような2つのソースファイルがあるとします。
inline111.cpp:
#include <iostream>
void bar();
inline int fun() {
return 111;
}
int main() {
std::cout << "inline111: fun() = " << fun() << ", &fun = " << (void*) &fun;
bar();
}
inline222.cpp:
#include <iostream>
inline int fun() {
return 222;
}
void bar() {
std::cout << "inline222: fun() = " << fun() << ", &fun = " << (void*) &fun;
}
ケースA:
コンパイル:
g++ -std=c++11 inline111.cpp inline222.cpp
出力:
inline111: fun() = 111, &fun = 0x4029a0
inline222: fun() = 111, &fun = 0x4029a0
ディスカッション:
インライン関数の定義が同じである必要がある場合でも、そうでない場合、C ++コンパイラはフラグを立てません(実際には、個別のコンパイルのため、チェックする方法がありません)。これを確実にするのはあなた自身の義務です!
リンカーは、として宣言されているように、単一定義規則について文句を言いません。ただし、inline111.cppはコンパイラによって処理される最初の変換ユニット(実際には呼び出します)であるため、コンパイラはinline111.cppで最初の呼び出しに遭遇するとインスタンス化されます。コンパイラがプログラム内の他の場所(inline222.cppなど)からの呼び出しを拡張しないことを決定した場合、への呼び出しは常にinline111.cpp(inline222.cpp内への呼び出し)から生成されたインスタンスにリンクされます。fun()
inline
fun()
fun()
fun()
fun()
fun()
その翻訳ユニットでインスタンスを生成することもできますが、リンクされないままになります)。確かに、それは同じ&fun = 0x4029a0
プリントアウトから明らかです。
最後にinline
、コンパイラーに実際にワンライナーを拡張fun()
するように提案されているにもかかわらず、それはあなたの提案を完全に無視しfun() = 111
ます。これは、両方の行にあるため明らかです。
ケースB:
コンパイル (逆順に注意):
g++ -std=c++11 inline222.cpp inline111.cpp
出力:
inline111: fun() = 222, &fun = 0x402980
inline222: fun() = 222, &fun = 0x402980
ディスカッション:
このケースは、ケースAで説明したことを主張します。
重要な点に注意してください。inline222.cppの実際の呼び出しをコメントアウトすると(たとえばfun()
、inline222.cppのcomment out -statementが完全に)、翻訳単位のコンパイル順序に関係なく、での最初の呼び出しの遭遇時にインスタンス化されます。inline111.cpp、結果としてケースBの出力が。cout
fun()
inline111: fun() = 111, &fun = 0x402980
ケースC:
コンパイル (通知-O2):
g++ -std=c++11 -O2 inline222.cpp inline111.cpp
また
g++ -std=c++11 -O2 inline111.cpp inline222.cpp
出力:
inline111: fun() = 111, &fun = 0x402900
inline222: fun() = 222, &fun = 0x402900
ディスカッション:
-O2
最適化により、コンパイラーはインライン化できる関数を実際に拡張-fno-inline
するように促されます(最適化オプションがない場合はデフォルトであることに注意してください)。ここでのアウトプリントから明らかなように、fun()
は実際にインライン展開されており(その特定の翻訳単位での定義に従って)、2つの異なる fun()
プリントアウトが生成されます。それにもかかわらず、同一のプリントアウトから明らかなように、(標準で要求されているように)グローバルにリンクされたインスタンスはまだ1つだけです。fun()
&fun
テンプレートの特殊化を行うときは、関数を明示的にインライン化する必要があります(特殊化が.hファイルにある場合)
1)今日では、ほとんどありません。関数をインライン化することをお勧めする場合、コンパイラーはユーザーの助けなしにそれを実行します。
2)常に。#1を参照してください。
(質問を2つの質問に分割したことを反映するように編集されました...)
C ++の関数/メソッドのキーワード「インライン」を記述すべきでないのはいつですか?
関数がヘッダーで宣言され、.cpp
ファイルで定義されている場合は、キーワードを記述しないでください。
コンパイラはいつ関数/メソッドを「インライン」にするかわからないのですか?
そのような状況はありません。コンパイラは関数をインライン化できません。関数への呼び出しの一部またはすべてをインライン化するだけです。関数のコードを取得していない場合は、これを行うことはできません(その場合、リンカーは、可能な場合はそれを行う必要があります)。
関数/メソッドの「インライン」を書き込むときに、アプリケーションがマルチスレッドであるかどうかは重要ですか?
いいえ、それはまったく問題ではありません。
これは、使用するコンパイラによって異なります。今日のコンパイラは人間よりもインライン化の方法をよく知っていると盲目的に信じないでください。最適化のヒントではなくリンケージディレクティブであるため、パフォーマンス上の理由から決して使用しないでください。私はイデオロギー的にこれらの議論が正しいことには同意しますが、現実に遭遇することは別のことかもしれません。
好奇心から複数のスレッドを読んだ後、作業中のコードに対するインラインの影響を試しました。その結果、GCCの速度は測定可能になり、Intelコンパイラの速度は上がりませんでした。
(詳細:クラス外で定義されたいくつかの重要な関数を使用した数学シミュレーション、GCC 4.6.3(g ++ -O3)、ICC 13.1.0(icpc -O3);重要なポイントにインラインを追加すると、GCCコードで+ 6%高速化されました)。
したがって、GCC 4.6を最新のコンパイラとして認定した場合、CPUを集中的に使用するタスクを記述し、ボトルネックがどこにあるかを正確に把握していれば、インラインディレクティブは依然として重要です。
実際には、ほとんどありません。あなたがしているのは、コンパイラが特定の関数をインライン化することを提案することだけです(たとえば、この関数へのすべての呼び出しを/ wその本体に置き換えます)。もちろん、保証はありません。コンパイラはディレクティブを無視する場合があります。
コンパイラーは通常、このようなものを検出して最適化するのに適しています。
gccは、最適化を有効にせずにコンパイルする場合、デフォルトでは関数をインライン化しません。Visual Studioについてはわかりません– deft_code
/FAcsを使用してコンパイルし、アセンブリコードを確認することで、Visual Studio 9(15.00.30729.01)についてこれを確認しました。コンパイラーは、デバッグモードで最適化を有効にせずにメンバー関数の呼び出しを生成しました。関数が__forceinlineでマークされている場合でも、インラインランタイムコードは生成されません。
F.5:関数が非常に小さく、タイムクリティカルな場合は、インラインで宣言します
理由:一部のオプティマイザーは、プログラマーからのヒントなしでインライン化するのが得意ですが、それに依存しません。測定!過去40年ほどの間、人間からのヒントなしに人間よりもうまくインライン化できるコンパイラが約束されてきました。私たちはまだ待ってます。インラインで指定すると(明示的に、またはクラス定義内にメンバー関数を書き込むときに暗黙的に)、コンパイラーはより良い仕事をするように促されます。
ソース:https ://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines.html#Rf-inline
例と例外については、ソースにアクセスしてください(上記を参照)。
継承で1つのユースケースが発生する可能性があります。たとえば、以下のすべてのケースが当てはまる場合:
次に、デストラクタを定義する必要があります。そうしないと、undefined referance
リンカーエラーが発生します。さらに、インラインキーワードを使用してデストラクタを定義するだけでなく、定義する必要があります。そうしないと、multiple definition
リンカーエラーが発生します。
これは、静的メソッドのみを含む一部のヘルパークラス、または基本例外クラスの記述などで発生する可能性があります。
例を挙げましょう:
Base.h:
class Base {
public:
Base(SomeElementType someElement) noexcept : _someElement(std::move(someElement)) {}
virtual ~Base() = 0;
protected:
SomeElementType _someElement;
}
inline Base::~Base() = default;
Derived1.h:
#include "Base.h"
class Derived1 : public Base {
public:
Derived1(SomeElementType someElement) noexcept : Base(std::move(someElement)) {}
void DoSomething1() const;
}
Derived1.cpp:
#include "Derived1.h"
void Derived1::DoSomething1() const {
// use _someElement
}
Derived2.h:
#include "Base.h"
class Derived2 : public Base {
public:
Derived2(SomeElementType someElement) noexcept : Base(std::move(someElement)) {}
void DoSomething2() const;
}
Derived2.cpp:
#include "Derived2.h"
void Derived2::DoSomething2() const {
// use _someElement
}
一般に、抽象クラスには、コンストラクタまたはデストラクタ以外の純粋仮想メソッドがいくつかあります。したがって、基本クラスの仮想デストラクタのデストラクタと定義を分離する必要はなく、virtual ~Base() = default;
クラスのデストラクタについて記述できます。しかし、私たちの場合はそうではありません。
私の知る限り、MSVCでは、クラスのデクレレーションについて次のような記述を行うことができますvirtual ~Base() = 0 {}
。したがって、インラインキーワードでデクレレーションと定義を分離する必要はありません。ただし、MSVCコンパイラでのみ機能します。
実例:
BaseException.h:
#pragma once
#include <string>
class BaseException : public std::exception {
public:
BaseException(std::string message) noexcept : message(std::move(message)) {}
virtual char const* what() const noexcept { return message.c_str(); }
virtual ~BaseException() = 0;
private:
std::string message;
};
inline BaseException::~BaseException() = default;
SomeException.h:
#pragma once
#include "BaseException.h"
class SomeException : public BaseException {
public:
SomeException(std::string message) noexcept : BaseException(std::move(message)) {}
};
SomeOtherException.h:
#pragma once
#include "BaseException.h"
class SomeOtherException : public BaseException {
public:
SomeOtherException(std::string message) noexcept : BaseException(std::move(message)) {}
};
main.cpp:
#include <SomeException.h>
#include <SomeOtherException.h>
#include <iostream>
using namespace std;
static int DoSomething(int argc) {
try {
switch (argc) {
case 0:
throw SomeException("some");
case 1:
throw SomeOtherException("some other");
default:
return 0;
}
}
catch (const exception& ex) {
cout << ex.what() << endl;
return 1;
}
}
int main(int argc, char**) {
return DoSomething(argc);
}
ライブラリを作成している場合や特別な理由がない限り、代わりにリンク時の最適化を忘れてinline
使用することができます。これにより、コンパイル単位間でインライン化するために関数定義がヘッダーに含まれている必要があるという要件がなくなります。これがまさに可能です。inline
(ただし、リンク時間最適化を使用しない理由はありますか?を参照してください。 )
C++インラインはCインラインとはまったく異なります。
#include <iostream>
extern inline int i[];
int i [5];
struct c {
int function (){return 1;} // implicitly inline
static inline int j = 3; // explicitly inline
static int k; // without inline, a static member has to be defined out of line
static int f (){return 1;} // but a static method does not // implicitly inline
};
extern inline int b;
int b=3;
int c::k = 3; // when a static member is defined out of line it cannot have a static
// specifier and if it doesn't have an `inline` specifier in the
// declaration or on the definition then it is not inline and always
// emits a strong global symbol in the translation unit
int main() {
c j;
std::cout << i;
}
inline
それ自体がコンパイラ、アセンブラ、およびリンカに影響します。これはコンパイラへのディレクティブであり、変換ユニットで使用されている場合はこの関数/データのシンボルのみを出力し、クラスメソッドと同様に、セクションに格納する.section .text.c::function(),"axG",@progbits,c::function(),comdat
か、ユニット化された.section .bss.i,"awG",@nobits,i,comdat
データに格納するようにアセンブラに指示します。.section .data.b,"awG",@progbits,b,comdat
初期化されたデータ。テンプレートのインスタンス化は、独自のcomdatグループにも含まれます。
これは次のとおり.section name, "flags"MG, @type, entsize, GroupName[, linkage]
です。たとえば、セクション名は.text.c::function()
です。axG
セクションが割り当て可能で実行可能であり、グループ内にあることを意味します。つまり、グループ名が指定されます(Mフラグがないため、entsizeは指定されません)。@progbits
セクションにデータが含まれていて、空白ではないことを意味します。c::function()
はグループ名であり、グループにはcomdat
リンケージとは、すべてのオブジェクトファイルで、comdatでタグ付けされたこのグループ名で検出されたすべてのセクションが、1を除いて最終実行可能ファイルから削除されることを意味します。つまり、コンパイラは、変換ユニットに定義が1つしかないことを確認してから、アセンブラに次のように指示します。オブジェクトファイル内の独自のグループ(1つのグループに1つのセクション)にある場合、リンカーは、オブジェクトファイルに同じ名前のグループがある場合は、最終的な.exeに1つだけ含めるようにします。inline
使用しない場合と使用しない場合の違いはinline
、通常の.data
または.text
ディレクティブによるアセンブラなど。外部リンケージを持つインラインシンボルのみに、このような外部comdatリンケージが与えられます。静的リンケージ(ローカル)シンボルは、comdatグループに入れる必要はありません。
inline
クラス内の非静的メソッド宣言で、メソッドがアウトオブラインで定義されている場合、メソッドはインラインになります。これにより、メソッドが変換ユニットで参照されていない場合、メソッドが変換ユニットで発行されるのを防ぎます。inline
アウトオブラインの定義を適用しても、同じ効果が得られます。メソッドが指定子なしでアウトオブラインで定義されinline
、クラスでの宣言がそうでないinline
場合、外部コマンドリンケージではなく外部リンケージを持つため、常に変換ユニット内のメソッドのシンボルが発行されます。メソッドがクラスで定義されている場合、それは暗黙的inline
にであり、外部リンケージではなく外部コマンドリンケージを提供します。
static inline
(メソッドではなく)クラスのメンバーで、それをstatic
メンバーにします(これは、そのリンケージを参照しません。クラスのリンケージがあり、外部である可能性があります)。また、クラスのメンバーをクラスで宣言してからアウトオブラインで定義する必要はなく、クラス内で定義することstatic inline
もできます(定義ではなく、なしでは許可されませんでした)。また、メンバーを作成しますが、 -ではありません。これは、定義が翻訳単位で参照されている場合にのみ発行されることを意味します。以前は、メンバーを作成するために、アウトオブライン定義で指定する必要がありました。static
static
-fpermissive
*static inline*
inline
static inline
inline
inline
inline
メソッドstatic
はクラスで定義できるため、クラスで定義されstatic inline
たメソッドには影響しません。このstatic
メソッドは常に外部リンケージを持ち、静的メソッドであり、ですinline
。それがライン外で定義されている場合、それinline
を作成するために使用する必要がありinline
(つまり、外部リンケージだけでなく外部comdatリンケージに与えるため)、static
それでも使用できません。
static inline
atファイルスコープはコンパイラにのみ影響します。これはコンパイラにとって意味があります。変換ユニットで使用されている場合にのみ、この関数/データのシンボルを発行し、通常の静的シンボルとして発行します(.globlディレクティブなしでin.text /.dataを格納します)。アセンブラにとって、との間に違いはありませstatic
んstatic inline
。のその他の形式と同様に、型でinline
ある、では使用できませんがclass
、そのクラスの型のオブジェクトでは使用できます。この形式はstatic inline
、関数のメンバーまたはメソッドでも使用できません。この場合、常にクラス内の他inline
の手段として扱われstatic
ます(つまり、クラスがメンバーまたはメソッドではなく、スコープとして機能していることを意味します。オブジェクトで使用されます)。
extern inline
参照されている場合、またはコンパイラエラーをスローする場合は、変換ユニットでこのシンボルを定義する必要があることを意味する宣言です。定義されている場合は、それを通常のものとして扱い、アセンブラーとリンカーにとってはとの間にinline
違いはないため、これはコンパイラーガードのみです。extern inline
inline
extern inline int i[];
extern int i[]; //allowed repetition of declaration with incomplete type, inherits inline property
extern int i[5]; //declaration now has complete type
extern int i[5]; //allowed redeclaration if it is the same complete type or has not yet been completed
extern int i[6]; //error, redeclaration with different complete type
int i[5]; //definition, must have complete type and same complete type as the declaration if there is a declaration with a complete type
エラー行のない上記の全体は、に折りたたまれinline int i[5]
ます。明らかに、extern inline int i[] = {5};
そうするとextern
、割り当てによる明示的な定義のために無視されます。
なしでアウトオブライン定義でstatic
許可されない理由は、静的がリンケージを参照していることを意味しているためだと思います。これは、それがクラスのメンバーであるかどうか、またはそのクラスが持っているかどうかがプログラマーにすぐにはわからないためです。ここで、は何か違うことを意味します。アウトオブライン定義の指定子を無視し、何の意味もありません。単純な整数の場合、名前空間の場合は名前空間から定義できませんが、関数の場合は、コード行からそれが行外であるかどうかを視覚的に判断する方法はありません。名前空間内の関数の定義static
-fpermissive
static
static
-fpermissive
static
k
c
k
static
リンケージ、または外部リンケージを持つ静的メンバーのアウトオブライン定義であり、コードのプログラマー/リーダーに間違った印象を与える可能性があります。
ローカルクラスの場合inline
、メンバー/メソッドでコンパイラエラーが発生し、メンバーとメソッドにリンクがありません。
inline
名前空間については、これとこれを参照してください
インラインキーワードは、コンパイラに関数呼び出しを関数の本体に置き換えるように要求し、最初に式を評価してから渡されます。リターンアドレスを格納する必要がなく、関数にスタックメモリが必要ないため、関数呼び出しのオーバーヘッドが削減されます。引数。
いつ使用するか:
- パフォーマンスを向上させるには
- 通話のオーバーヘッドを減らすため。
- コンパイラへの単なる要求であるため、特定の関数はインライン化されません*大きな関数
- 条件付き引数が多すぎる関数
- 再帰的なコードやループのあるコードなど。
リターンタイプの前に、最初に配置します。しかし、ほとんどのコンパイラはそれを無視します。それが定義されていて、コードのブロックが小さい場合、ほとんどのコンパイラーはそれをインラインと見なします。
コードを開発およびデバッグするときは、省略inline
してください。デバッグが複雑になります。
それらを追加する主な理由は、生成されたコードの最適化を支援することです。通常、これはコードスペースの増加と速度のトレードオフになりますがinline
、コードスペースと実行時間の両方を節約できる場合もあります。
アルゴリズムが完了する前にパフォーマンスの最適化についてこの種の考えを費やすことは、時期尚早の最適化です。
インライン化する必要がある場合:
1.パラメータの受け渡し、制御の転送、制御の戻りなど、関数が呼び出されたときに発生するオーバーヘッドを回避したい場合。
2.関数は小さく、頻繁に呼び出され、インラインにすることは非常に有利です。80-20の法則に従って、プログラムのパフォーマンスに大きな影響を与える関数をインラインにするようにしてください。
インラインは登録と同様にコンパイラへの単なる要求であり、オブジェクトコードサイズでコストがかかることがわかっています。