web-dev-qa-db-fra.com

Le compilateur n'échoue pas en repoussant un std :: unique_ptr dans un std :: vector

Un unique_ptr Ne peut pas être repoussé dans un std::vector Car il n'est pas copiable, sauf si std::move Est utilisé. Cependant, que F soit une fonction qui renvoie un unique_ptr, L'opération std::vector::Push_back(F()) est autorisée. Voici un exemple ci-dessous:

#include <iostream>
#include <vector>
#include <memory>

class A {
  public:
    int f() { return _f + 10; }

  private:
    int _f = 20;
};

std::unique_ptr<A> create() { return std::unique_ptr<A>(new A); }


int main() {
  std::unique_ptr<A> p1(new A());

  std::vector< std::unique_ptr<A> > v;

  v.Push_back(p1); // (1) This fails, should use std::move

  v.Push_back(create()); // (2) This doesn't fail, should use std::move?

  return 0;
}

(2) Est autorisé, mais (1) Ne l'est pas. Est-ce parce que la valeur retournée est déplacée d'une manière ou d'une autre implicitement?

Dans (2), Est-il réellement nécessaire d'utiliser std::move?

22
Dan

std::move(X) signifie essentiellement "ici, traitez X comme s'il s'agissait d'un objet temporaire".

create() renvoie un std::unique_ptr<A> temporaire pour commencer, donc move n'est pas nécessaire.


Si vous voulez en savoir plus, regardez dans catégories de valeurs . Votre compilateur utilise des catégories de valeurs pour déterminer si une expression fait référence à un objet temporaire ("rvalue") ou non ("lvalue").

p1 Est une valeur l et create() est une valeur r.

34
HolyBlackCat

std::vector::Push_back() a une surcharge qui prend en entrée une référence rvalue:

void Push_back( T&& value );

La valeur de retour de create() est une valeur temporaire sans nom, c'est-à-dire une valeur r, elle peut donc être transmise telle quelle à Push_back() sans avoir besoin d'utiliser std::move() dessus.

std::move() n'est nécessaire que lors du passage d'une variable nommée, c'est-à-dire une lvalue, où une rvalue est attendue.

8
Remy Lebeau

Avec C++ 11, nous avons obtenu des constructeurs de mouvements et une sémantique de rvalues.

std :: move (X) est juste un transtypage en une rvalue qui convertit X en X && c'est tout. Ensuite, move ctor prend le relais et les constructeurs de déplacement "volent" généralement les ressources détenues par l'argument. unique_ptr possède un ctor de déplacement.

Les valeurs de retour de fonction sont déjà une rvalue (sauf si la fonction renvoie une référence lvalue comme indiqué par @HolyBlackCat dans les commentaires) qui déclenchera le déplacement du ctor sans avoir besoin de cast supplémentaire. Et puisque move ctor est défini pour unique_ptr, il sera compilé.

Également la raison pour laquelle v.Push_back (p1); échec est: vous essayez d'appeler le constructeur de copie avec une valeur l et il échoue parce que unique_ptr n'a pas de ctor de copie.

6
Kadir Erdem Demir

Il convient également de savoir que cela fonctionnerait également en raison de la capacité du compilateur à déplacer des objets qui ne se déplacent pas explicitement (NRVO)

#include <iostream>
#include <vector>
#include <memory>

class A {
  public:
    int f() { return _f + 10; }

  private:
    int _f = 20;
};

std::unique_ptr<A> create() {
    std::unique_ptr<A> x (new A);
    return x; 

}


int main() {
  std::unique_ptr<A> p1(new A());

  std::vector< std::unique_ptr<A> > v;

  //v.Push_back(p1); // (1) This fails, should use std::move

  v.Push_back(create()); // (2) This doesn't fail, should use std::move?

  return 0;
}
2
NewMe