web-dev-qa-db-fra.com

Utilisation de std :: forward vs std :: move

Je lis toujours que std::forward est uniquement destiné aux paramètres de modèle. Cependant, je me demandais pourquoi. Voir l'exemple suivant:

void ImageView::setImage(const Image& image){
    _image = image;
}

void ImageView::setImage(Image&& image){
    _image = std::move(image);
}

Ce sont deux fonctions qui font essentiellement la même chose; l'un prend une référence de valeur l, l'autre une référence de valeur r. Maintenant, je pensais depuis std::forward est censé renvoyer une référence de valeur l si l'argument est une référence de valeur l et une référence de valeur r si l'argument est un, ce code pourrait être simplifié en quelque chose comme ceci:

void ImageView::setImage(Image&& image){
    _image = std::forward(image);
}

Ce qui est un peu similaire à l'exemple mentionné par cplusplus.com pour std::forward (juste sans aucun paramètre de modèle). Je voudrais juste savoir si cela est correct ou non, et sinon pourquoi.

Je me demandais également quelle serait exactement la différence

void ImageView::setImage(Image& image){
    _image = std::forward(image);
}
52
bweber

Vous ne pouvez pas utiliser std::forward Sans spécifier explicitement son argument de modèle. Il est utilisé intentionnellement dans un contexte non déduit.

Pour comprendre cela, vous devez vraiment comprendre comment les références de transfert (T&& Pour un T déduit) fonctionnent en interne, et ne pas les agiter comme "c'est magique". Alors regardons ça.

template <class T>
void foo(T &&t)
{
  bar(std::forward<T>(t));
}

Disons que nous appelons foo comme ceci:

foo(42);

42 Est une valeur de type int. T est déduit de int. L'appel à bar utilise donc int comme argument de modèle pour std::forward. Le type de retour de std::forward<U> Est U &&. Dans ce cas, c'est int &&, Donc t est transmis en tant que valeur r.

Maintenant, appelons foo comme ceci:

int i = 42;
foo(i);

i est une valeur de type int. En raison de la règle spéciale pour un transfert parfait, lorsqu'une valeur de type V est utilisée pour déduire T dans un paramètre de type T &&, V & Est utilisé pour déduction. Par conséquent, dans notre cas, T est déduit comme étant int &.

Par conséquent, nous spécifions int & Comme argument de modèle pour std::forward. Son type de retour sera donc "int & &&", Qui se réduira à int &. C'est une lvalue, donc i est transféré en tant que lvalue.

Résumé

Pourquoi cela fonctionne avec les modèles lorsque vous faites std::forward<T>, T est parfois une référence (lorsque l'original est une valeur l) et parfois non (lorsque l'original est une valeur r). std::forward Sera donc converti en une référence lvalue ou rvalue selon le cas.

Vous ne pouvez pas faire fonctionner cela dans la version non-modèle précisément parce que vous n'aurez qu'un seul type disponible. Sans parler du fait que setImage(Image&& image) n'accepterait pas du tout les valeurs l - une valeur l ne peut pas se lier aux références rvalue.

71
Angew

Je recommande de lire "Effective Modern C++", son auteur est Scott Meyers.

Point 23: Comprendre std :: move et std :: forward.

Point 24: Distinguer les références universelles des références de valeur.

D'un point de vue purement technique, la réponse est oui: std :: forward peut tout faire. std :: move n'est pas nécessaire. Bien sûr, aucune fonction n'est vraiment nécessaire, car nous pourrions écrire des transtypages partout, mais j'espère que nous sommes d'accord que ce serait, eh bien, dégoûtant. les attractions de std :: move sont la commodité, la probabilité d'erreur réduite et une plus grande clarté.

rvalue-reference: Cette fonction accepte rvalues ​​ne peut pas accepter lvalues.

void ImageView::setImage(Image&& image){
    _image = std::forward(image); //error 
    _image = std::move(image);//conventional
    _image = std::forward<Image>(image);//unconventional

}

Notez d'abord que std :: move ne nécessite qu'un argument de fonction, tandis que std :: forward requiert à la fois un argument de fonction et un argument de type de modèle.

template <typename T> void ImageView::setImage(T&& image){
    _image = std::forward<T>(image);
}

références universelles (références de transfert): Cette fonction accepte tout et effectue un transfert parfait.

22
Ron Tang

Vous devez spécifier le type de modèle dans std::forward.

Dans ce contexte Image&& image est toujours une référence de valeur r et std::forward<Image> se déplacera toujours, vous pourriez donc aussi bien utiliser std::move.

Votre fonction acceptant une référence de valeur r ne peut pas accepter de valeurs l, elle n'est donc pas équivalente aux deux premières fonctions.

7
Chris Drew