web-dev-qa-db-fra.com

Pourquoi le retour d'un std :: optional se déplace-t-il parfois et parfois copie?

Voir l'exemple ci-dessous de retour d'une option de UserName - une classe mobile/copiable.

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();
}

Pourquoi return {u} provoquer une copie et return u un mouvement?

Voici l'exemple de coliru associé: http://coliru.stacked-crooked.com/a/6bf853750b38d11

Un autre cas (grâce au commentaire de @Slava):

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

Parce que renvoyer un nom d'un objet avec une durée de stockage automatique est traité comme renvoyant une valeur r de l'objet. Notez que cela ne fonctionne que si l'expression dans l'instruction de retour est un nom (éventuellement entre parenthèses, sans les accolades), comme return u; Ou return (u);, donc return {u}; Fonctionne comme d'habitude, c'est-à-dire que le constructeur de copie est invoqué.

Partie associée dans la norme [class.copy.elision]/ :

Dans les contextes d'initialisation de copie suivants, une opération de déplacement peut être utilisée à la place d'une opération de copie:

  • Si l'expression dans une instruction de retour ([stmt.return]) est une (éventuellement entre parenthèses) id-expression qui nomme un objet avec une durée de stockage automatique déclarée dans le corps ou la clause de déclaration de paramètre de la fonction englobante la plus interne ou de l'expression lambda, ou
  • ...

la résolution de surcharge pour sélectionner le constructeur pour la copie est d'abord effectuée comme si l'objet était désigné par une valeur r.

27
xskxzr

C'est une sorte de braced-init-list. [dcl.init.list] /1.

Pour être encore plus précis, c'est une " expr-or-braced-init-list [dcl.init]/1

d'une déclaration de retour "[stmt.return]/2

Une instruction return avec tout autre opérande ne doit être utilisée que dans une fonction dont le type de retour n'est pas cv void; l'instruction return initialise le résultat glvalue ou l'objet résultat prvalue de l'appel de fonction (explicite ou implicite) par copie-initialisation à partir de opérande .

À partir de ce point, permettez-moi de répondre à la réponse de xskxzr qui mentionne [class.copy.elision]/

Dans les contextes suivants d'initialisation de copie , une opération de déplacement peut être utilisée à la place d'une opération de copie:

  • Si l'expression dans une instruction de retour ([stmt.return]) est une expression id (éventuellement entre parenthèses) qui nomme un objet avec une durée de stockage automatique déclarée dans le corps ou la clause de déclaration de paramètre de la fonction englobante la plus interne ou de l'expression lambda , ou

En termes humains normaux, la raison pour laquelle la copie est appelée au lieu de déplacer parce que braced-init-list appelle u qui se trouve être lvalue.

Donc, vous voudrez peut-être savoir si braced-init-list appelez u c'est-à-dire rvalue ...

return {std::move(u)};

Eh bien, u est déplacé vers une nouvelle valeur r de UserName et la copie d'élision fonctionne juste après.

Cela prend donc un mouvement comme dans

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();
}

impression

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, ...};

est copie-liste-initialisation. l'objet (retour) est initialisé à partir de la liste d'initialisation par copie-initialisation pour copie-liste-initialisation

1
Zang MingJie