回答:
これは、一般的にベストプラクティスを推奨するために使用される1をするためのconst参照によって使用パスのすべての種類のタイプ(組み込みを除き、char
、int
、double
イテレータのため、など)、および関数オブジェクトのために(から派生ラムダ、クラスstd::*_function
)。
これは、移動セマンティクスが存在する前は特に当てはまりました。理由は単純です。値で渡す場合は、オブジェクトのコピーを作成する必要があり、非常に小さなオブジェクトを除いて、これは参照を渡すよりも常にコストがかかるためです。
C ++ 11では、移動のセマンティクスが得られました。簡単に言えば、移動のセマンティクスにより、場合によっては、オブジェクトをコピーせずに「値で」渡すことができます。特に、これは、渡すオブジェクトが右辺値の場合です。
それ自体、オブジェクトの移動には、参照渡しと同じくらいのコストがかかります。ただし、多くの場合、関数は内部的にオブジェクトをコピーします。つまり、引数の所有権を取得します。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 */
}
__restrict__
過度のコピーを行うよりも、GCC (参照でも機能する)を使用したい。あまりにも悪い標準C ++はC99のrestrict
キーワードを採用していません。
指摘されているように、種類によって異なります。組み込みデータ型の場合、値で渡すのが最適です。ペアのintなどの一部の非常に小さな構造でも、値で渡すとパフォーマンスが向上します。
以下に例を示します。整数値があり、それを別のルーチンに渡したいとします。その値がレジスタに格納されるように最適化されている場合、参照として渡す場合は、最初にメモリに格納してから、呼び出しを実行するためにスタックに配置されたそのメモリへのポインタを格納する必要があります。値で渡されている場合、必要なのはスタックにプッシュされたレジスターだけです。(詳細は、さまざまな呼び出しシステムとCPUを指定した場合よりも少し複雑です)。
テンプレートプログラミングを行っている場合、渡される型がわからないため、通常は常にconst refを渡さざるを得ません。値によって何かを渡すことによるペナルティの受け渡しは、組み込み型を渡すペナルティよりもはるかに悪いです。 const ref。
これは、非テンプレート関数のインターフェースを設計するときに私が通常作業するものです。
関数がパラメーターを変更する必要がなく、値のコピーが簡単な場合(int、double、float、char、boolなど)は、値渡しします。std:: string、std :: vector、およびその他の標準ライブラリのコンテナにはありません)
値のコピーにコストがかかり、関数がポイントされた値を変更する必要がなく、NULLが関数が処理する値である場合は、constポインターを渡します。
値のコピーにコストがかかり、関数が指す値を変更する必要があり、NULLが関数が処理する値である場合は、非constポインターを渡します。
値のコピーにコストがかかり、関数が参照される値を変更する必要がなく、代わりにポインターが使用された場合、NULLは有効な値ではない場合は、const参照で渡します。
値のコピーにコストがかかり、関数が参照される値を変更する必要があり、代わりにポインターが使用されている場合、NULLは有効な値ではない場合、非const参照で渡します。
std::optional
画像に追加すると、ポインタは不要になります。
あなたはあなたの答えを得たように聞こえます。値渡しは高価ですが、必要な場合に使用できるコピーを提供します。
原則として、const参照によるパスの方が優れています。ただし、関数の引数をローカルで変更する必要がある場合は、値渡しを使用することをお勧めします。いくつかの基本的なタイプの場合、パフォーマンスは値渡しでも参照渡しでも一般的に同じです。実際には、ポインターによって内部的に表現された参照を参照します。これにより、たとえば、ポインターの両方のパフォーマンスが同じであることが期待できます。また、不要な逆参照が原因で、値による受け渡しがより高速になる場合もあります。
経験則として、非クラス型の値とクラスのconst参照。クラスが本当に小さい場合は、値で渡す方が適切ですが、違いはごくわずかです。本当に避けたいのは、巨大なクラスを値で渡し、それをすべて複製することです。たとえば、かなりの数の要素を含むstd :: vectorを渡す場合、これは大きな違いになります。
std::vector
実際にアイテムをヒープに割り当て、ベクターオブジェクト自体は決して成長しません。あ、ちょっと待って。ただし、操作によってベクターのコピーが作成される場合は、実際にはすべての要素が複製されます。それは悪いだろう。
sizeof(std::vector<int>)
定数ですが、値で渡すと、コンパイラの賢さがない場合でもコンテンツがコピーされます。
小さな型の値渡し。
大きな型のconst参照で渡す(bigの定義はマシン間で異なる可能性があります)しかし、C ++ 11では、移動のセマンティクスを利用できるため、データを消費する場合は値で渡します。例えば:
class Person {
public:
Person(std::string name) : name_(std::move(name)) {}
private:
std::string name_;
};
これで、呼び出しコードは次のようになります。
Person p(std::string("Albert"));
そして、1つのオブジェクトだけが作成されname_
、クラスのメンバーに直接移動されPerson
ます。const参照で渡す場合は、それをに入れるためにコピーを作成する必要がありますname_
。
単純な違い:-関数には入力パラメーターと出力パラメーターがあるため、渡す入力パラメーターと出力パラメーターが同じ場合は、参照渡しを使用します。入力パラメーターと出力パラメーターが異なる場合は、値渡しを使用する方が適切です。
例 void amount(int account , int deposit , int total )
入力パラメーター:アカウント、預金出力パラメーター:合計
入力と出力は、vauleによる異なる使用呼び出しです
void amount(int total , int deposit )
入力合計預金出力合計