c++ unique_ptr argument en passant
Supposons que j'ai le code suivant:
class B { /* */ };
class A {
vector<B*> vb;
public:
void add(B* b) { vb.push_back(b); }
};
int main() {
A a;
B* b(new B());
a.add(b);
}
Supposons que, dans ce cas, tous les pointeurs B*
peut être gérée par le biais d' unique_ptr<B>
.
Étonnamment, je n'ai pas réussi à trouver comment faire pour convertir ce code en utilisant unique_ptr
. Après quelques essais, je suis venu avec le code suivant, qui compile:
class A {
vector<unique_ptr<B>> vb;
public:
void add(unique_ptr<B> b) { vb.push_back(move(b)); }
};
int main() {
A a;
unique_ptr<B> b(new B());
a.add(move(b));
}
Donc ma question est simple: est-ce la façon de le faire et, en particulier, est move(b)
la seule façon de le faire? (Je pensais à des références rvalue mais je ne comprends pas tout.)
Et si vous avez un lien avec des explications complètes de la sémantique de déplacement, unique_ptr
, etc. que je n'étais pas en mesure de trouver, n'hésitez pas à le partager.
MODIFIER Selon http://thbecker.net/articles/rvalue_references/section_01.html, mon code semble être OK.
En fait, std::move est juste sucre syntaxique. Avec un objet x de la classe X, move(x)
est la même chose que:
static_cast <X&&>(x)
Ces 2 déplacez fonctions sont nécessaires, car un casting pour une rvalue reference:
- empêche la fonction "ajouter" de passage par valeur
- fait
push_back
utiliser la valeur par défaut de déplacer le constructeur de B
Apparemment, je n'ai pas besoin de la deuxième std::move
dans mon main()
si je change ma "ajouter" fonction passage par référence (ordinaire lvalue réf.).
Je voudrais une confirmation de tout cela, mais...
Merci, la question a donné des liens intéressants (voir EDIT)
Vous pouvez éviter l'
std::move
principale, si vous avez changé d'ajouter cette façon, mais ce serait mauvais, parce que l'appelant de add
n'ai aucune idée si ils toujours la propriété de l'objet ou pas, sans chercher à la source de add
.Argh. Je savais que j'allais faire quelque chose de stupide. Merci. BTW, je pensais passer ma paramètre par référence rvalue, c'est à dire "ajouter(B&& b)", dans le but de supprimer le "déplacer" dans sa mise en œuvre, qui semble redondant dans ce cas. Mais, ce faisant (en passant par rvalue réf plutôt que par valeur), j'obtiens les mêmes résultats, et en particulier, je ne peux pas supprimer ce "mouvement". Quelle en est la raison ?
Parce que toutes les variables sont lvalues, y compris ceux qui sont des références à des rvalues.
OriginalL'auteur Bérenger | 2012-09-22
Vous devez vous connecter pour publier un commentaire.
Oui, c'est comment il devrait être fait. Vous êtes expressément le transfert de la propriété de
main
àA
. C'est fondamentalement le même que votre code précédent, sauf que c'est plus explicite et beaucoup plus fiable.OriginalL'auteur Puppy
Je suis un peu surpris de voir que ce n'est pas une réponse très clairement et explicitement ici, ni sur n'importe quel endroit-je facilement tombé sur. Alors que je suis assez nouveau à ce genre de choses, je pense que le suivant peut être dit.
La situation est un appel de fonction qui construit un
unique_ptr<T>
valeur (éventuellement par le casting du résultat d'un appel à l'new
), et veut passer à une fonction qui va prendre possession de l'objet pointé (en les stockant dans une structure de données par exemple, comme cela se passe ici dans unevector
). Pour indiquer que la propriété a été obtenu par l'appelant, et il est prêt à le céder, le passage d'ununique_ptr<T>
valeur est en place. Il ya aussi loin que je peux voir trois raisonnable modes de passage d'une telle valeur.add(unique_ptr<B> b)
dans la question.const
lvalue de référence, comme dansadd(unique_ptr<B>& b)
add(unique_ptr<B>&& b)
En passant par
const
lvalue de référence ne serait pas raisonnable, car il ne permet pas la fonction appelée à prendre en main (etconst
référence rvalue serait encore plus idiot que je ne suis même pas sûr que c'est autorisé).Autant que le code est valide va, les options 1 et 3 sont presque équivalents: ils la force de l'appelant d'écrire une rvalue comme argument à l'appel, éventuellement en l'enveloppant d'une variable dans un appel à
std::move
(si l'argument est déjà une rvalue, c'est à dire, sans nom, comme dans un casting de la suite denew
, ce n'est pas nécessaire). Dans l'option 2 cependant, le passage d'une rvalue (éventuellement à partir d'std::move
) est pas permis, et la fonction doit être appelée avec un nomunique_ptr<T>
variable (lors du passage d'une fonte à partir denew
, on doit attribuer à une variable en premier).Quand
std::move
est en effet utilisée, la variable contenant leunique_ptr<T>
valeur en l'appelant est conceptuellement déréférencé (converti en rvalue, respectivement en fonte de référence rvalue), et la propriété est donnée jusqu'à ce point. Dans l'option 1. le déréférencement est réel, et la valeur est déplacé à une temporaire qui est passé à la fonction appelée (si les rues de la fonction serait d'inspecter la variable en l'appelant, il trouverait-elle contenir un pointeur null déjà). La propriété a été transférée, et il n'y a aucun moyen de l'appelant pouvait décider de ne pas l'accepter (ne rien faire avec l'argument des causes de la pointe-de valeur pour être détruit à la sortie de la fonction; l'appel de larelease
méthode sur l'argument permettrait d'éviter cela, mais serait tout simplement suite à une fuite de mémoire). Étonnamment, les options 2. et 3. sont sémantiquement équivalent lors de l'appel de la fonction, bien qu'ils nécessitent une syntaxe différente pour l'appelant. Si la fonction appelée permettrait de passer l'argument à une autre fonction prenant une rvalue (comme lepush_back
méthode),std::move
doit être inséré dans les deux cas, ce qui permettra de transférer la propriété à ce point. Si la fonction appelée oubliez pas de faire n'importe quoi avec l'argument, alors le visiteur se retrouve toujours propriétaires de l'objet, si la tenue d'un nom pour elle (comme c'est obligatoire dans l'option 2); cela en dépit du fait que dans le cas 3, depuis le prototype de fonction a demandé à l'appelant d'accord pour la libération de la propriété (soit en appelantstd::move
ou la fourniture d'une temporaire). En résumé, les méthodes neconst
de référence) à l'abandonner; mais ce n'est pas explicite (pas d'appel destd::move
nécessaire ou permis), ni de prendre de suite la propriété assurée. Je considère cette méthode plutôt floue, dans son intention, sauf s'il est expressément prévu que la prise de possession ou non est laissé à la discrétion de la fonction appelée (certains utilisent peut être imaginé, mais les appelants doivent être conscients)L'Option 3 est assez clairement son intention; à condition que la propriété est en fait pris, c'est pour moi la meilleure solution. Il est légèrement plus efficace que 1 dans la mesure où aucun pointeur valeurs sont déplacés temporaires (les appels à
std::move
sont en fait des moulages et ne coûtent rien), ce qui peut être particulièrement pertinente si le pointeur est transmis à travers plusieurs intermédiaires fonctions avant que son contenu est en fait déplacé.Voici un peu de code pour expérimenter avec.
Comme l'écrit, la production est
Note que les valeurs sont détruits dans la stockées dans le vecteur. Essayez de modifier le prototype de la méthode
add
(adaptation de tout autre code, si nécessaire, à faire de la compilation), et si oui ou non il passe sur son argumentb
. Plusieurs permutations de lignes de sortie peut être obtenue.OriginalL'auteur Marc van Leeuwen
Shameless plug, recherchez la rubrique "se Déplacer dans les membres". Il décrit exactement votre scénario.
OriginalL'auteur fredoverflow
Votre code dans
main
pourrait être simplifié un peu, depuis le C++14:où vous pouvez mettre des arguments pour
B
'constructeur à l'intérieur de l'intérieur des parenthèses.Vous pourriez aussi envisager une fonction membre de classe qui prend possession d'un pointeur brut:
et le code correspondant dans
main
serait:Une autre option est d'utiliser de transfert parfait pour l'ajout de vecteur de membres:
et le code principal:
où, comme avant, vous pourriez mettre des arguments du constructeur pour
B
à l'intérieur des parenthèses.Lien vers exemple de travail
OriginalL'auteur M.M