@JDługoszがコメントで指摘しているように、ハーブは別の(後で?)講演で他のアドバイスを提供します。おおよそここからhttps://youtu.be/xnqTKD8uD64?t=54m50sを参照してください。
彼のアドバイスはf
、これらのシンク引数から構成要素を移動すると仮定して、いわゆるシンク引数を取る関数の値パラメーターのみを使用することになります。
この一般的なアプローチでは、左辺値と右辺値の引数にf
合わせて調整された最適な実装と比較して、左辺値と右辺値の引数の両方に移動コンストラクタのオーバーヘッドが追加されるだけです。これが当てはまる理由を確認するためにf
、値のパラメーターを取るとしますT
。
void f(T x) {
T y{std::move(x)};
}
f
lvalue引数を指定して呼び出すと、コピーコンストラクターが呼び出されて構成されx
、移動コンストラクターが呼び出されて構成されy
ます。一方、f
右辺値引数を指定して呼び出すと、moveコンストラクターが呼び出され、constructが呼び出されx
、別のmoveコンストラクターが呼び出されて、constructが呼び出されy
ます。
一般に、f
lvalue引数の最適な実装は次のとおりです。
void f(const T& x) {
T y{x};
}
この場合、を構築するために1つのコピーコンストラクターのみが呼び出されy
ます。f
for rvalue引数の最適な実装は、再び一般的に次のようになります。
void f(T&& x) {
T y{std::move(x)};
}
この場合、を構築するために1つの移動コンストラクタのみが呼び出されy
ます。
したがって、賢明な妥協案は、値パラメーターを取り、最適な実装に関してlvalueまたはrvalue引数のいずれか1つの追加moveコンストラクター呼び出しを持つことです。これは、Herbの講演で与えられたアドバイスでもあります。
@JDługoszがコメントで指摘したように、値による受け渡しは、sink引数からオブジェクトを構築する関数に対してのみ意味があります。f
引数をコピーする関数がある場合、値渡しのアプローチは、一般的な定数渡しのアプローチよりもオーバーヘッドが大きくなります。f
パラメータのコピーを保持する関数の値渡しアプローチは、次の形式になります。
void f(T x) {
T y{...};
...
y = std::move(x);
}
この場合、左辺値引数にはコピー構築と移動割り当てがあり、右辺値引数には移動構築と移動割り当てがあります。左辺値引数の最も最適なケースは次のとおりです。
void f(const T& x) {
T y{...};
...
y = x;
}
これは、割り当てのみに要約されます。これは、値渡しアプローチに必要なコピーコンストラクターと移動割り当てよりもはるかに安価になる可能性があります。この理由は、割り当てはで既存の割り当てられたメモリを再利用する可能性がy
あり、そのため(割り当て解除)ができないのに対し、コピーコンストラクターは通常メモリを割り当てるからです。
右辺値引数の場合f
、コピーを保持するための最適な実装は次の形式です。
void f(T&& x) {
T y{...};
...
y = std::move(x);
}
したがって、この場合は移動割り当てのみです。右辺値をそのバージョンに渡すと、f
const参照が取得され、移動割り当てではなく割り当てのみがかかります。相対的に言えば、f
この場合、一般的な実装としてconst参照を使用するバージョンが望ましいです。
したがって、一般的に、最適な実装を行うには、トークで示したように、オーバーロードするか、ある種の完全な転送を行う必要があります。欠点はf
、引数の値カテゴリをオーバーロードすることを選択した場合のパラメーターの数に応じて、必要なオーバーロードの数が増えることです。完全な転送には、f
テンプレート関数になり、仮想化を妨げるという欠点があり、100%正しくしたい場合は、コードが大幅に複雑になります(詳細については、トークを参照してください)。