C ++では、値渡しまたは定数参照渡しの方が良いですか?


213

C ++では、値渡しまたは定数参照渡しの方が良いですか?

どっちがいい練習なんだろう。変数のコピーを作成しないため、定数参照による受け渡しはプログラムのパフォーマンスを向上させるはずだと私は理解しています。


回答:


203

これは、一般的にベストプラクティスを推奨するために使用される1をするためのconst参照によって使用パスのすべての種類のタイプ(組み込みを除き、charintdoubleイテレータのため、など)、および関数オブジェクトのために(から派生ラムダ、クラスstd::*_function)。

これは、移動セマンティクスが存在する前は特に当てはまりました。理由は単純です。値で渡す場合は、オブジェクトのコピーを作成する必要があり、非常に小さなオブジェクトを除いて、これは参照を渡すよりも常にコストがかかるためです。

C ++ 11では、移動のセマンティクスが得られました。簡単に言えば、移動のセマンティクスにより、場合によっては、オブジェクトをコピーせずに「値で」渡すことができます。特に、これは、渡すオブジェクトが右辺値の場合です。

それ自体、オブジェクトの移動には、参照渡しと同じくらいのコストがかかります。ただし、多くの場合、関数は内部的にオブジェクトをコピーします。つまり、引数の所有権取得します。2

これらの状況では、次の(簡略化された)トレードオフがあります。

  1. オブジェクトを参照で渡し、内部的にコピーできます。
  2. オブジェクトを値で渡すことができます。

「値渡し」では、オブジェクトが右辺値でない限り、オブジェクトがコピーされます。右辺値の場合、オブジェクトを代わりに移動できるため、2番目のケースは突然「コピーしてから移動する」ではなく、「移動してから(場合によっては)再度移動する」ようになります。

適切な移動コンストラクター(ベクター、文字列など)を実装する大きなオブジェクトの場合、2番目のケースは最初のケースよりもはるかに効率的です。したがって、関数が引数の所有権を取得し、オブジェクトタイプが効率的な移動をサポートする場合は、値渡し使用することをお勧めします


歴史的なメモ:

実際、最新のコンパイラは、値による受け渡しが高価である場合を理解し、可能であればconst refを使用するように呼び出しを暗黙的に変換できる必要があります。

理論的には。実際には、コンパイラーは、関数のバイナリー・インターフェースを壊さずにこれを常に変更できるわけではありません。一部の特殊なケース(関数がインライン化されている場合)では、元のオブジェクトが関数内のアクションによって変更されないことがコンパイラーにわかる場合、コピーは実際には省略されます。

しかし、一般的にコンパイラーはこれを判別できません。C++の移動セマンティクスの出現により、この最適化の関連性ははるかに低くなりました。


1たとえば、Scott Meyers、Effective C ++

2これは特に、オブジェクトコンストラクターに当てはまります。オブジェクトコンストラクターは、引数を取り、それらを内部に格納して、構築されたオブジェクトの状態の一部にすることができます。


うーん...参考文献を渡すことの価値があるかどうかはわかりません。double-s
sergtk 2008年

3
いつものように、ブーストはここで役立ちます。boost.org/doc/libs/1_37_0/libs/utility/call_traits.htmには、型が組み込み型であることを自動的に判断するためのテンプレートスタッフが含まれています(テンプレートでは便利で、簡単にはわからない場合があります)。
CesarB 2008年

13
この答えは重要なポイントを逃しています。スライスを回避するには、参照(constまたはその他)で渡す必要があります。stackoverflow.com/questions/274626/…を
ChrisN

6
@クリス:そうです。完全に異なるセマンティクスであるため、ポリモーフィズムの全体を省略しました。OPは(意味的に)「値渡し」の引数の引き渡しを意味すると思います。他のセマンティクスが必要な場合、問題自体は提起されません。
Konrad Rudolph、

98

編集: 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の記事にある一連の良いルールをご覧ください(強くお勧めします)。

  • 関数が引数を副作用として変更する場合は、非const参照で引数を変更してください。
  • 関数が引数を変更せず、引数がプリミティブ型である場合は、値で受け取ります。
  • それ以外の場合は、次の場合を除き、const参照で取得します。
    • 関数がconst参照のコピーをとにかく作成する必要がある場合は、値で取得します。

上記の「プリミティブ」とは、基本的に数バイトの長さで、ポリモーフィックではない(イテレーター、関数オブジェクトなど)か、コピーにコストがかかる小さいデータ型を意味します。その論文には、もう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 */
}

私はあなたのルールは良いと思いましたが、リファレンスとしてそれをスピードアップするので渡さないことについて話している最初の部分については確信が持てません。確かにそうですが、最適化の参照だけの参照として何かを渡さないのはまったく意味がありません。渡すスタックオブジェクトを変更する場合は、refを使用して行います。そうしない場合は、値で渡します。変更したくない場合は、const-refとして渡します。参照として渡すときに他のものが得られるため、値渡しによる最適化は重要ではありません。「スピードが欲しい」がわかりません。これらの操作を実行する場合は、とにかく値渡しします
。–チクバ

ヨハネス:私は愛して、私はそれを読んだときにその記事を、私はそれをしようとしたとき、私は失望しました。このコードは、GCCとMSVCの両方で失敗しました。私は何かを逃したのですか、それとも実際には機能しませんか?
user541686 2012

とにかくコピーを作成したい場合は、(const refではなく)値で渡してから移動することに同意しません。このように見て、コピーと移動のどちらがより効率的であるか(転送すると、2つのコピーを持つこともできます)、または単にコピーですか?はい、どちらかの側にいくつかの特別なケースがありますが、とにかくデータを移動できない場合(例:整数のトンを持つPOD)、追加のコピーは必要ありません。
Ion Todirel 2012年

2
Mehrdad、あなたが何を期待していたのかは
わかり

型が言語の欠陥と重複しないことをコンパイラに納得させるためだけにコピーする必要があると思います。__restrict__過度のコピーを行うよりも、GCC (参照でも機能する)を使用したい。あまりにも悪い標準C ++はC99のrestrictキーワードを採用していません。
ルスラン2017

12

タイプによって異なります。参照と逆参照を行う必要があるというわずかなオーバーヘッドを追加しています。既定のコピーctorを使用しているポインターと同じかそれより小さいサイズの型の場合、値で渡す方がおそらく高速です。


非ネイティブ型の場合、(コンパイラーがコードを最適化する方法に応じて)参照だけでなくconst参照を使用してパフォーマンスを向上させることができます。
OJ。

9

指摘されているように、種類によって異なります。組み込みデータ型の場合、値で渡すのが最適です。ペアのintなどの一部の非常に小さな構造でも、値で渡すとパフォーマンスが向上します。

以下に例を示します。整数値があり、それを別のルーチンに渡したいとします。その値がレジスタに格納されるように最適化されている場合、参照として渡す場合は、最初にメモリに格納してから、呼び出しを実行するためにスタックに配置されたそのメモリへのポインタを格納する必要があります。値で渡されている場合、必要なのはスタックにプッシュされたレジスターだけです。(詳細は、さまざまな呼び出しシステムとCPUを指定した場合よりも少し複雑です)。

テンプレートプログラミングを行っている場合、渡される型がわからないため、通常は常にconst refを渡さざるを得ません。値によって何かを渡すことによるペナルティの受け渡しは、組み込み型を渡すペナルティよりもはるかに悪いです。 const ref。


用語に関する注意:100万の整数を含む構造体は、依然として「PODタイプ」です。おそらく、「組み込み型の場合、値で渡すのが最善」ということです。
スティーブジェソップ

6

これは、非テンプレート関数のインターフェースを設計するときに私が通常作業するものです。

  1. 関数がパラメーターを変更する必要がなく、値のコピーが簡単な場合(int、double、float、char、boolなど)は、値渡しします。std:: string、std :: vector、およびその他の標準ライブラリのコンテナにはありません)

  2. 値のコピーにコストがかかり、関数がポイントされた値を変更する必要がなく、NULLが関数が処理する値である場合は、constポインターを渡します。

  3. 値のコピーにコストがかかり、関数が指す値を変更する必要があり、NULLが関数が処理する値である場合は、非constポインターを渡します。

  4. 値のコピーにコストがかかり、関数が参照される値を変更する必要がなく、代わりにポインターが使用された場合、NULLは有効な値ではない場合は、const参照で渡します。

  5. 値のコピーにコストがかかり、関数が参照される値を変更する必要があり、代わりにポインターが使用されている場合、NULLは有効な値ではない場合、非const参照で渡します。


std::optional画像に追加すると、ポインタは不要になります。
バイオレットキリン

5

あなたはあなたの答えを得たように聞こえます。値渡しは高価ですが、必要な場合に使用できるコピーを提供します。


なぜこれが否決されたのか分かりませんか?それは私には理にかなっています。現在保存されている値が必要な場合は、値で渡します。そうでない場合は、参照を渡します。
21:46のTotty

4
それは完全にタイプに依存しています。POD(プレーンオールドデータ)タイプを参照で実行すると、実際にはメモリアクセスが増えるため、パフォーマンスが低下する可能性があります。
Torlack、2008年

1
明らかに参照によってintを渡しても何も保存されません!この質問は、ポインタよりも大きなことを意味していると思います。
GeekyMonkey 2008年

4
それほど明白ではありません。コンピューターがどのようにconst refを使用して単純なものを渡すのかを本当に理解していない人が多くのコードを見たことがあります。
Torlack、2008年

4

原則として、const参照によるパスの方が優れています。ただし、関数の引数をローカルで変更する必要がある場合は、値渡しを使用することをお勧めします。いくつかの基本的なタイプの場合、パフォーマンスは値渡しでも参照渡しでも一般的に同じです。実際には、ポインターによって内部的に表現された参照を参照します。これにより、たとえば、ポインターの両方のパフォーマンスが同じであることが期待できます。また、不要な逆参照が原因で、値による受け渡しがより高速になる場合もあります。


呼び出し先のパラメーターのコピーを変更する必要がある場合は、値で渡すのではなく、呼び出されたコードでコピーを作成できます。IMOでは通常、そのような実装の詳細に基づいてAPIを選択するべきではありません。呼び出しコードのソースはどちらの方法でも同じですが、そのオブジェクトコードはそうではありません。
スティーブジェソップ

値渡しする場合はコピーが作成されます。IMOは、どの方法でコピーを作成しても問題ありません。値渡しまたはローカルでの引数渡し-これがC ++に関係します。しかし、デザインの観点からは、私はあなたに同意します。ただし、ここではC ++の機能についてのみ説明し、デザインには触れません。
sergtk 2008年

1

経験則として、非クラス型の値とクラスのconst参照。クラスが本当に小さい場合は、値で渡す方が適切ですが、違いはごくわずかです。本当に避けたいのは、巨大なクラスを値で渡し、それをすべて複製することです。たとえば、かなりの数の要素を含むstd :: vectorを渡す場合、これは大きな違いになります。


私の理解では、std::vector実際にアイテムをヒープに割り当て、ベクターオブジェクト自体は決して成長しません。あ、ちょっと待って。ただし、操作によってベクターのコピーが作成される場合は、実際にはすべての要素が複製されます。それは悪いだろう。
Steven Lu

1
はい、それは私が考えていたものです。sizeof(std::vector<int>)定数ですが、値で渡すと、コンパイラの賢さがない場合でもコンテンツがコピーされます。
Peter

1

小さな型の値渡し。

大きな型の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_


-5

単純な違い:-関数には入力パラメーターと出力パラメーターがあるため、渡す入力パラメーターと出力パラメーターが同じ場合は、参照渡しを使用します。入力パラメーターと出力パラメーターが異なる場合は、値渡しを使用する方が適切です。

void amount(int account , int deposit , int total )

入力パラメーター:アカウント、預金出力パラメーター:合計

入力と出力は、vauleによる異なる使用呼び出しです

  1. void amount(int total , int deposit )

入力合計預金出力合計

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.