web-dev-qa-db-ja.com

なぜstd :: optionalを返すのが時々移動し、時にはコピーするのですか?

オプションのUserName-移動可能/コピー可能クラスを返す以下の例を参照してください。

std::optional<UserName> CreateUser()
{
   UserName u;
   return {u}; // this one will cause a copy of UserName
   return u;   // this one moves UserName
}


int main()
{
   auto d = CreateUser();
}

どして return {u}はコピーを行い、return u 動き?

関連するcoliruサンプルは次のとおりです。 http://coliru.stacked-crooked.com/a/6bf853750b38d11

別のケース(@Slavaからのコメントのおかげ):

std::unique_ptr<int> foo() 
{ 
    std::unique_ptr<int> p; 
    return {p};  // uses copy of unique_ptr and so it breaks...
}
26
fen

自動保存期間を持つオブジェクトのnameを返すことは、オブジェクトの右辺値を返すものとして扱われるためです。これは、returnステートメント内の式がreturn u;return (u);のような(括弧で囲まれていない可能性がある)名前である場合にのみ機能するため、return {u};は通常どおり機能します。

標準の関連部分 [class.copy.elision]/

次のコピー初期化コンテキストでは、コピー操作の代わりに移動操作が使用される場合があります。

  • Returnステートメント([stmt.return])の式が、(おそらく括弧で囲まれた)id-expressionであり、自動ストレージ期間が宣言されたオブジェクトを指定する場合最も内側の囲み関数またはラムダ式の本体またはパラメーター宣言節、または
  • ...

オブジェクトが右辺値で指定されているかのように、コピーのコンストラクターを選択するオーバーロード解決が最初に実行されます。

27
xskxzr

これは一種のbraced-init-listです。 [dcl.init.list] /1.

さらに具体的には、"expr-or-braced-init-list[ dcl.init]/1

return statement"[stmt.return]/2

他のオペランドを持つreturnステートメントは、戻り値の型がcv voidではない関数でのみ使用されます。 return文は、copy-initializationからoperand

この点から、 [class.copy.elision]/ に言及するxskxzrの回答を引用します。

次のcopy-initialization contextsでは、move操作が代わりに使用される可能性があります代わりにコピー操作:

  • Returnステートメント([stmt.return])の式が、最も内側の関数またはラムダ式のbodyまたはparameter-declaration-clauseで宣言された自動ストレージ期間を持つオブジェクトを指定する(おそらく括弧で囲まれた)id式である場合、または

通常の人間の言葉では、braced-init-listがたまたま左辺値であるuを呼び出すため、移動ではなくコピーが呼び出される理由。

したがって、braced-init-list call uつまりrvalue ...かどうかを知りたい場合があります。

return {std::move(u)};

さて、uUserNameの新しい右辺値に移動され、コピー省略はすぐに機能します。

したがって、これは次のように1つの動きが必要です。

return u;

godbolt.org/g/b6stLr

wandbox.org/permlink/7u1cPc0TG9gqToZD

#include <iostream>
#include <optional>

struct UserName
{
  int x;
  UserName() : x(0) {};
  UserName(const UserName& other) : x(other.x) { std::cout << "copy " << x << "\n"; };
  UserName(UserName&& other)      : x(other.x) { std::cout << "move "  << x << "\n"; };
};

std::optional<UserName> CreateUser()
{
  UserName u;
  return u;   // this one moves UserName
}

std::optional<UserName> CreateUser_listinit()
{
  UserName u;
  auto whatever{u};
  return whatever;
}

std::optional<UserName> CreateUser_listinit_with_copy_elision()
{
  UserName u;
  return {u};
}

std::optional<UserName> CreateUser_move_listinit_with_copy_elision()
{
  UserName u;
  return {std::move(u)};
}

int main()
{
  std::cout << "CreateUser() :\n";
  [[maybe_unused]] auto d = CreateUser();

  std::cout << "\nCreateUser_listinit() :\n";
  [[maybe_unused]] auto e = CreateUser_listinit();

  std::cout << "\nCreateUser_listinit_with_copy_elision() :\n";
  [[maybe_unused]] auto f = CreateUser_listinit_with_copy_elision();

  std::cout << "\nCreateUser_move_listinit_with_copy_elision() :\n";
  [[maybe_unused]] auto g = CreateUser_move_listinit_with_copy_elision();
}

印刷する

CreateUser() :
move 0

CreateUser_listinit() :
copy 0
move 0

CreateUser_listinit_with_copy_elision() :
copy 0

CreateUser_move_listinit_with_copy_elision() :
move 0
5
sandthorn

return {arg1、arg2、...};

iscopy-list-initialization (戻り)オブジェクトは、copy-list-initializationのcopy-initializationによって初期化子リストから初期化されます。

1
Zang MingJie