J'ai vu du code quelque part dans lequel quelqu'un a décidé de copier un objet et de le déplacer par la suite vers une donnée membre d'une classe. Cela m'a laissé dans la confusion en ce sens que je pensais que tout l'intérêt de bouger était d'éviter de copier. Voici l'exemple:
struct S
{
S(std::string str) : data(std::move(str))
{}
};
Voici mes questions:
- Pourquoi ne prenons-nous pas une référence à rvalue
str
? - Une copie ne coûtera-t-elle pas cher, surtout avec quelque chose comme
std::string
? - Quelle serait la raison pour laquelle l'auteur déciderait de faire une copie puis un déménagement?
- Quand dois-je faire cela moi-même?
c++
c++11
move-semantics
user2030677
la source
la source
Réponses:
Avant de répondre à vos questions, une chose que vous semblez vous tromper: prendre par valeur en C ++ 11 ne signifie pas toujours copier. Si une rvalue est passée, elle sera déplacée (à condition qu'un constructeur de déplacement viable existe) plutôt que d'être copiée. Et
std::string
a un constructeur de mouvement.Contrairement à C ++ 03, en C ++ 11, il est souvent idiomatique de prendre des paramètres par valeur, pour les raisons que je vais expliquer ci-dessous. Consultez également ces questions-réponses sur StackOverflow pour obtenir un ensemble de directives plus générales sur la façon d'accepter les paramètres.
Parce que cela rendrait impossible la transmission de lvalues, comme dans:
S'il y
S
avait seulement un constructeur qui accepte rvalues, ce qui précède ne compilerait pas.Si vous passez une rvalue, celle-ci sera déplacée vers
str
, et éventuellement déplacée versdata
. Aucune copie ne sera effectuée. Si vous passez une lvalue, par contre, cette lvalue sera copiée dansstr
, puis déplacée dansdata
.Donc, pour résumer, deux mouvements pour les valeurs, une copie et un mouvement pour les valeurs.
Tout d'abord, comme je l'ai mentionné ci-dessus, le premier n'est pas toujours une copie; et ceci dit, la réponse est: " Parce que c'est efficace (les déplacements d'
std::string
objets sont bon marché) et simple ".Sous l'hypothèse que les mouvements sont bon marché (en ignorant ici SSO), ils peuvent être pratiquement ignorés lors de l'examen de l'efficacité globale de cette conception. Si nous le faisons, nous avons une copie pour lvalues (comme nous l'aurions fait si nous acceptions une référence lvalue à
const
) et aucune copie pour rvalues (alors que nous aurions toujours une copie si nous acceptions une référence lvalue àconst
).Cela signifie que prendre par valeur est aussi bon que prendre par référence
const
lvalue quand lvalues sont fournies, et mieux quand rvalues sont fournies.PS: Pour fournir un certain contexte, je crois que c'est le Q&R auquel le PO fait référence.
la source
const T&
passage d'argument: dans le pire des cas (lvalue) c'est la même chose, mais dans le cas d'un temporaire, vous n'avez qu'à déplacer le temporaire. Gagnant-gagnant.data
membre)? Vous en auriez une copie même si vous preniez par référence lvaleur àconst
data
!Pour comprendre pourquoi c'est un bon modèle, nous devons examiner les alternatives, à la fois en C ++ 03 et en C ++ 11.
Nous avons la méthode C ++ 03 pour prendre un
std::string const&
:dans ce cas, il y aura toujours une seule copie effectuée. Si vous construisez à partir d'une chaîne C brute, un
std::string
sera construit, puis copié à nouveau: deux allocations.Il existe la méthode C ++ 03 pour prendre une référence à a
std::string
, puis la permuter dans un localstd::string
:c'est la version C ++ 03 de "move semantics", et
swap
peut souvent être optimisée pour être très bon marché (un peu comme amove
). Il doit également être analysé dans son contexte:et vous oblige à former un non-temporaire
std::string
, puis à le jeter. (Un temporairestd::string
ne peut pas se lier à une référence non-const). Une seule allocation est cependant effectuée. La version C ++ 11 prendrait un&&
et vous demanderait de l'appeler avecstd::move
, ou avec un temporaire: cela nécessite que l'appelant crée explicitement une copie en dehors de l'appel et déplace cette copie dans la fonction ou le constructeur.Utilisation:
Ensuite, nous pouvons faire la version complète de C ++ 11, qui prend en charge à la fois la copie et
move
:Nous pouvons ensuite examiner comment cela est utilisé:
Il est assez clair que cette technique de surcharge est au moins aussi efficace, sinon plus, que les deux styles C ++ 03 ci-dessus. Je vais doubler cette version à 2 surcharges la version "la plus optimale".
Maintenant, nous allons examiner la version à prendre par copie:
dans chacun de ces scénarios:
Si vous comparez cette version côte à côte avec la version "la plus optimale", nous en faisons exactement une de plus
move
! Pas une seule fois, nous ne faisons un extracopy
.Donc, si nous supposons que
move
c'est bon marché, cette version nous offre presque les mêmes performances que la version la plus optimale, mais 2 fois moins de code.Et si vous prenez par exemple 2 à 10 arguments, la réduction du code est exponentielle - 2x fois moins avec 1 argument, 4x avec 2, 8x avec 3, 16x avec 4, 1024x avec 10 arguments.
Maintenant, nous pouvons contourner cela via un transfert parfait et SFINAE, vous permettant d'écrire un seul constructeur ou modèle de fonction qui prend 10 arguments, fait SFINAE pour s'assurer que les arguments sont de types appropriés, puis les déplace ou les copie dans le état local selon les besoins. Bien que cela évite l'augmentation de mille fois du problème de la taille du programme, il peut encore y avoir toute une pile de fonctions générées à partir de ce modèle. (les instanciations de fonction de modèle génèrent des fonctions)
Et de nombreuses fonctions générées signifient une taille de code exécutable plus grande, ce qui peut en soi réduire les performances.
Pour le coût de quelques
move
secondes, nous obtenons un code plus court et presque les mêmes performances, et souvent un code plus facile à comprendre.Maintenant, cela ne fonctionne que parce que nous savons, lorsque la fonction (dans ce cas, un constructeur) est appelée, que nous voulons une copie locale de cet argument. L'idée est que si nous savons que nous allons faire une copie, nous devrions informer l'appelant que nous faisons une copie en la plaçant dans notre liste d'arguments. Ils peuvent alors s'optimiser autour du fait qu'ils vont nous en donner une copie (en se déplaçant dans notre argumentation, par exemple).
Un autre avantage de la technique de «prise par valeur» est que les constructeurs de déplacement sont souvent noexcept. Cela signifie que les fonctions qui prennent par valeur et sortent de leur argument peuvent souvent être noexcept, déplaçant les
throw
s hors de leur corps et dans la portée d'appel (qui peut parfois l'éviter via la construction directe, ou construire les éléments etmove
dans l'argument, pour contrôler où se produit le lancer).la source
noexcept
. En prenant des données par copie, vous pouvez créer votre fonctionnoexcept
et faire en sorte que toute construction de copie provoquée des lancements potentiels (comme une mémoire insuffisante) se produise en dehors de votre appel de fonction.Ceci est probablement intentionnel et est similaire à l' idiome de copie et d'échange . Fondamentalement, puisque la chaîne est copiée avant le constructeur, le constructeur lui-même est exceptionnellement sûr car il échange (déplace) uniquement la chaîne temporaire str.
la source
Vous ne voulez pas vous répéter en écrivant un constructeur pour le déplacement et un pour la copie:
C'est beaucoup de code standard, surtout si vous avez plusieurs arguments. Votre solution évite cette duplication sur le coût d'un déménagement inutile. (L'opération de déplacement devrait cependant être assez bon marché.)
L'idiome concurrent est d'utiliser une transmission parfaite:
Le template magic choisira de se déplacer ou de copier en fonction du paramètre que vous passez. Il s'étend essentiellement à la première version, où les deux constructeurs ont été écrits à la main. Pour plus d'informations, voir l'article de Scott Meyer sur les références universelles .
Du point de vue des performances, la version de transmission parfaite est supérieure à votre version car elle évite les déplacements inutiles. Cependant, on peut affirmer que votre version est plus facile à lire et à écrire. L'impact possible sur les performances ne devrait pas avoir d'importance dans la plupart des situations, de toute façon, cela semble donc être une question de style à la fin.
la source