yohhoyの日記

2012-05-24

参照渡し or 値渡し?

C++03/11における関数の引数型とコピー/ムーブ処理コストとの関係について。

本記事の内容はC++Now 2012 Keynote: "Moving Forward with C++11"スライド資料(Part I, Part II)に基づく。(Part IIのpp.22-57)


型Tに対する変更操作を行う関数において、引数の型をconst参照渡し(const T&*1または値渡し(T)とするどちらが“良い”デザインかという話。ここでは関数呼び出しから値を返すまでに生じるコピーコンストラクタ/ムーブコンストラクタの呼び出し回数によって評価する。つまり、回数が少ない方が低コスト=良いデザインという観点にたつ。

// const参照渡し; pass-by-const-reference
T modify1(const T& x)
{
  T tmp(x);
  tmp.modify();
  return tmp;
}
// 値渡し; pass-by-value
T modify2(T x)
{
  x.modify();
  return x;
}

なおC++標準規格が許容する戻り値最適化(RVO; Return Value Optimization)/名前付き戻り値最適化(NRVO; Named Return Value Optimization)*2、コンパイラによって常に行われるものと仮定する。


要約

  • C++03:通常は const T& でよい。ただしT型が小さくtrivialな場合は T の方が低コスト。(intなどの組込み型ではconst int&よりも単にintの方が低コスト。)
  • C++11:コピー操作よりも低コストなムーブ操作があり、かつ実引数が常にlvalueという使われ方でなければ、T による値渡しが有効。
  • C++11:const lvalue参照渡し(const T&), rvalue参照渡し(T&&)の2種類をオーバーロード関数として提供すると、ムーブまたはコピー 1回 まで削減される。
  • 「常に値渡しを使うこと」や「値渡しは禁止」は共に正確でなく、関数インタフェース設計の問題である。


C++03

lvaluervalue
const T&1 copy1 copy
T2 copy1 copy
T modify1(const T& x)
{
  T tmp(x);    // copy発生
  tmp.modify();
  return tmp;  // (NRVO)
}

T modify2(T x)  // lvalueのときcopy発生
{
  x.modify();
  return x;     // copy発生
}

T t0;
modify1(t0);   // lvalue
modify1(T());  // rvalue

C++11

lvaluexvalueprvalue
const T&1 copy1 copy1 copy
T1 copy
1 move
2 move1 move
T modify1(const T& x)
{
  T tmp(x);    // copy発生
  tmp.modify();
  return tmp;  // (NRVO)
}

T modify2(T x)  // lvalueのときcopy発生、xvalueのときmove発生
{
  x.modify();
  return x;     // move発生
}

T t0;
modify1(t0);             // lvalue
modify1(std::move(t0));  // xvalue
modify1(T());            // prvalue


C++11: 関数オーバーロード

lvaluexvalueprvalue
const T&1 copy1 copy1 copy
T1 copy
1 move
2 move1 move
const T&
T&&
1 copy1 move1 move
// [A] const lvalue参照(const T&)
T modify3(const T& x)
{
  T tmp(x);    // copy発生
  tmp.modify();
  return tmp;  // (NRVO)
}

// [B] rvalue参照(T&&)
T modify3(T&& x)
{
  x.modify();
  return std::move(x);  // move発生
}

T t0;
modify3(t0);             // lvalue  → [A]
modify3(std::move(t0));  // xvalue  → [B]
modify3(T());            // prvalue → [B]

*1:C++11ではlvalue参照とrvalue参照は区別されるため、ここでは「const lvalue参照渡し」が正確。

*2:RVO/NRVOは関数戻り値のコピー省略による最適化手法の名称であり、C++11標準規格上は"copy elision"と表記している。(N3337 12.8/p31)

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証