C++では、値渡しと定数参照渡しのどちらが良いですか?
私はどちらがより良い習慣なのか疑問に思っています。定数参照による受け渡しは、変数のコピーを作成していないため、プログラムのパフォーマンスが向上するはずです。
C++では、値渡しと定数参照渡しのどちらが良いですか?
私はどちらがより良い習慣なのか疑問に思っています。定数参照による受け渡しは、変数のコピーを作成していないため、プログラムのパフォーマンスが向上するはずです。
以前は、組み込み型 ( 、、など)、反復子、および関数オブジェクト(ラムダ、 から派生したクラス) を除くすべての型にconst ref による渡しを使用することが、一般的に推奨されるベスト プラクティス1でした。char
int
double
std::*_function
これは、移動セマンティクスが存在する前は特に当てはまりました。理由は簡単です。値渡しの場合、オブジェクトのコピーを作成する必要があり、非常に小さなオブジェクトを除いて、これは常に参照を渡すよりもコストがかかります。
C++11 では、ムーブ セマンティクスが得られました。簡単に言えば、移動セマンティクスにより、場合によっては、オブジェクトをコピーせずに「値渡し」で渡すことができます。特に、これは、渡すオブジェクトがrvalueの場合です。
オブジェクトを移動すること自体は、少なくとも参照渡しと同じくらいコストがかかります。ただし、多くの場合、関数はオブジェクトを内部的にコピーします。つまり、引数の所有権を取得します。2
これらの状況では、次の (簡略化された) トレードオフがあります。
オブジェクトが右辺値でない限り、「値渡し」でもオブジェクトがコピーされます。右辺値の場合、代わりにオブジェクトを移動できるため、2 番目のケースは突然「コピーしてから移動する」ではなく、「移動してから (潜在的に) 再度移動する」ようになります。
適切な移動コンストラクタ (ベクトル、文字列など) を実装する大きなオブジェクトの場合、2 番目のケースは最初のケースよりもはるかに効率的です。したがって、関数が引数の所有権を取得し、オブジェクト タイプが効率的な移動をサポートしている場合は、値渡しを使用することをお勧めします。
歴史的なメモ:
実際、最新のコンパイラは、値渡しが高価な場合を把握し、可能であれば呼び出しを const ref を使用するように暗黙的に変換できるはずです。
理論的には。実際には、関数のバイナリ インターフェイスを壊さずにコンパイラが常にこれを変更できるとは限りません。一部の特殊なケース (関数がインライン化されている場合) では、関数内のアクションによって元のオブジェクトが変更されないことをコンパイラが判断できる場合、コピーは実際には省略されます。
しかし、一般に、コンパイラはこれを判断できず、C++ での移動セマンティクスの出現により、この最適化の関連性が大幅に低下しました。
1たとえば、Scott Meyers では、Effective C++ .
2これは、オブジェクト コンストラクターに特によく当てはまります。オブジェクト コンストラクターは、引数を取り、それらを構築されたオブジェクトの状態の一部として内部に格納する場合があります。
編集: cpp-next に関する Dave Abrahams による新しい記事:
コピーが安価な構造体の値渡しには、オブジェクトが別名ではない (同じオブジェクトではない) とコンパイラが想定する可能性があるという追加の利点があります。参照渡しを使用すると、コンパイラは常にそれを想定できません。簡単な例:
foo * f;
void bar(foo g) {
g.i = 10;
f->i = 2;
g.i += 5;
}
コンパイラはそれを最適化できます
g.i = 15;
f->i = 2;
f と g が同じ場所を共有していないことがわかっているためです。g が参照 (foo &) の場合、コンパイラはそれを想定できませんでした。gi は f->i によってエイリアス化され、7 の値を持たなければならないため、コンパイラは gi の新しい値をメモリから再取得する必要があります。
より実用的なルールについては、Move Constructorsの記事 (強くお勧めします) にある優れたルール セットを参照してください。
上記の「プリミティブ」とは、基本的に数バイトの長さであり、多態的 (反復子、関数オブジェクトなど) ではないか、コピーに費用がかかる小さなデータ型を意味します。その論文には、もう1つのルールがあります。アイデアは、コピーを作成したい場合 (引数を変更できない場合) と、コピーしたくない場合 (引数が一時的なものであった場合に関数で引数自体を使用したい場合) です。 、 例えば)。この論文では、それを行う方法について詳しく説明しています。C++1x では、その手法を言語サポートでネイティブに使用できます。それまでは、上記のルールに従います。
例: 文字列を大文字にして大文字バージョンを返すには、常に値で渡す必要があります: いずれにせよ、そのコピーを取得する必要があります (const 参照を直接変更することはできません)。呼び出し元が可能な限り最適化できるように、そのコピーを早期に作成します。詳細は次のとおりです。
my::string uppercase(my::string s) { /* change s and return it */ }
ただし、とにかくパラメーターを変更する必要がない場合は、const を参照してください。
bool all_uppercase(my::string const& s) {
/* check to see whether any character is uppercase */
}
ただし、パラメータの目的が引数に何かを書き込むことである場合は、非 const 参照で渡します。
bool try_parse(T text, my::string &out) {
/* try to parse, write result into out */
}
種類によります。参照と逆参照を行う必要があるという小さなオーバーヘッドを追加しています。デフォルトのコピー ctor を使用しているポインターと同じかそれより小さいサイズの型の場合、おそらく値渡しの方が高速です。
指摘されているように、種類によって異なります。組み込みデータ型の場合、値を渡すのが最適です。intのペアなど、一部の非常に小さな構造でも、値を渡すことでパフォーマンスが向上します。
次に例を示します。整数値があり、それを別のルーチンに渡したいとします。その値がレジスタに格納されるように最適化されている場合、それを参照として渡す場合は、最初にその値をメモリに格納し、次にスタックに配置されたそのメモリへのポインタを呼び出して実行する必要があります。値が渡されていた場合、必要なのはレジスタがスタックにプッシュされることだけです。(詳細は、さまざまな呼び出しシステムとCPUを指定した場合よりも少し複雑です)。
テンプレートプログラミングを行っている場合、渡される型がわからないため、通常は常にconst refを渡す必要があります。値によって悪いものを渡すことに対するペナルティを渡すことは、組み込み型を渡すことによるペナルティよりもはるかに悪いです。 constrefによる。
あなたの答えが得られたようですね。値による受け渡しはコストがかかりますが、必要な場合に使用するコピーを提供します。
原則として、const参照を渡す方が適切です。ただし、関数の引数をローカルで変更する必要がある場合は、値の受け渡しを使用することをお勧めします。一部の基本的なタイプでは、値の受け渡しと参照による受け渡しの両方で、パフォーマンスは一般的に同じです。実際には、ポインターによって内部的に表される参照です。そのため、たとえば、ポインターの両方の受け渡しはパフォーマンスの点で同じであるか、不必要な逆参照のために値による受け渡しがより高速になることが期待できます。
小さい型の値渡し。
big 型の場合は const 参照で渡します (big の定義はマシンによって異なる場合があります)。ただし、C++11 では、移動セマンティクスを利用できるため、データを消費する場合は値で渡します。例えば:
class Person {
public:
Person(std::string name) : name_(std::move(name)) {}
private:
std::string name_;
};
これで、呼び出しコードは次のようになります。
Person p(std::string("Albert"));
name_
そして、1 つのオブジェクトのみが作成され、 class のmember に直接移動されPerson
ます。const 参照で渡す場合は、コピーを作成して に配置する必要がありますname_
。
経験則として、非クラス型の値とクラスの const 参照。クラスが非常に小さい場合は、おそらく値渡しの方がよいでしょうが、違いは最小限です。本当に避けたいのは、いくつかの巨大なクラスを値で渡し、それをすべて複製することです。たとえば、かなりの数の要素を含む std::vector を渡す場合、これは大きな違いになります。