コピー省略を使用すると、コピーコンストラクターまたはムーブコンストラクターに観測可能な副作用がある場合でも、実装はそのコピー構築またはムーブ構築を省略できます。C++17より前は、コピー省略の各ケースが許可されていましたが、必須ではありませんでした。C++17ではprvalueの意味論が変更され、それまで一時オブジェクトの省略として扱われていた複数のケースが、代わりに直接構築となりました。
返されるprvalueと名前付きローカル変数 #
C++17以降、関数の戻り値型のprvalueを関数から返すと、結果オブジェクトが直接構築されます。したがって、コピーコンストラクターとムーブコンストラクターが削除されていても、この形式は妨げられません。ただし、return文の時点で、返される型のデストラクターにはアクセス可能であり、削除されていない必要があります。
struct Token {
Token();
Token(const Token&) = delete;
Token(Token&&) = delete;
};
Token make_token() {
return Token{}; // C++17以降は保証された直接構築
}名前付き戻り値最適化(NRVO)は、C++17以降でも引き続き省略可能です。
struct Widget {};
Widget make_named_widget() {
Widget widget;
return widget; // NRVOが適用される可能性がある。使用可能なコピーまたはムーブが必要
}NRVOが適用されない場合、return文内の適格なローカル変数は、オーバーロード解決ではrvalueとして扱われます。この動作を要求するためだけに return std::move(token); と記述してはいけません。これはNRVOを妨げる可能性があり、NRVOの対象となる名前から式をxvalueへ変更します。名前付きローカル変数をそのまま返してください。
インターフェースとライフタイムの設計 #
呼び出し側が独立した結果を受け取る場合は、完成した値を返してください。
#include <string>
std::string make_label(int id) {
return "item-" + std::to_string(id);
}戻り値はローカルオブジェクトへの参照ではありません。参照戻り値が正しいのは、参照先オブジェクトが呼び出しより長く存続し、借用またはエイリアス化が意図した契約である場合だけです。
const Config& active_config(); // より長いライフタイムを持つストレージを参照する
自動変数であるローカル変数へのlvalue参照またはrvalue参照を返してはいけません。rvalue参照は、return後までローカルオブジェクトのライフタイムを延長せず、参照カテゴリーを伝えるだけです。逆に、一時オブジェクトをローカルの const T& または T&& に束縛すると、その一時オブジェクトのライフタイムはそのローカル参照のライフタイムまで延長されますが、関数から返される参照のライフタイムまで延長されるわけではありません。
値返しのインターフェースは、防御的なムーブを使わずに記述し、実証されたコストだけを測定してください。C++17の保証は、規定されたprvalueのケースに適用されます。NRVO、基底サブオブジェクトの初期化、異なる結果型への変換は別のケースであり、それぞれ固有のコピーまたはムーブ要件があります。
コメント
GitHub で閲覧・コメントする(投稿にはログインが必要です)