Je pense que la seule chose que les autres réponses n'ont pas suffisamment soulignée est le point de vitesse .
std::shared_ptr
le nombre de références est atomique . l'augmentation ou la diminution du nombre de références nécessite un incrément ou un décrément atomique . C'est cent fois plus lent que l' incrémentation / décrémentation non atomique , sans compter que si nous incrémentons et décrémentons le même compteur, nous nous retrouvons avec le nombre exact, gaspillant une tonne de temps et de ressources dans le processus.
En déplaçant le shared_ptr
au lieu de le copier, nous "volons" le nombre de références atomiques et nous annulons l'autre shared_ptr
. "voler" le compte de référence n'est pas atomique , et c'est cent fois plus rapide que de copier le shared_ptr
(et de provoquer un incrément ou une diminution de référence atomique ).
Notez que cette technique est utilisée uniquement à des fins d'optimisation. le copier (comme vous l'avez suggéré) est tout aussi fin en termes de fonctionnalité.
En utilisant,
move
vous évitez d'augmenter, puis de diminuer immédiatement, le nombre d'actions. Cela pourrait vous faire économiser des opérations atomiques coûteuses sur le nombre d'utilisation.la source
Les opérations de déplacement (comme le constructeur de déplacement) pour
std::shared_ptr
sont bon marché , car ce sont essentiellement des "pointeurs de vol" (de la source à la destination; pour être plus précis, tout le bloc de contrôle d'état est "volé" de la source à la destination, y compris les informations de comptage de référence) .Au lieu de cela, copiez les opérations sur
std::shared_ptr
invoquer l' augmentation du nombre de références atomiques (c'est-à-dire pas seulement++RefCount
sur unRefCount
membre de données entier , mais par exemple en appelantInterlockedIncrement
sur Windows), ce qui est plus coûteux que de simplement voler des pointeurs / état.Donc, en analysant la dynamique du nombre de ref de ce cas en détail:
Si vous passez
sp
par valeur puis en prenez une copie à l'intérieur de laCompilerInstance::setInvocation
méthode, vous avez:shared_ptr
paramètre est construit par copie: ref count incrément atomique .shared_ptr
paramètre dans le membre de données: ref count atomic increment .shared_ptr
paramètre est détruit: ref count décrément atomique .Vous avez deux incréments atomiques et un décrément atomique, pour un total de trois opérations atomiques .
Au lieu de cela, si vous passez le
shared_ptr
paramètre par valeur, puisstd::move
à l'intérieur de la méthode (comme correctement fait dans le code de Clang), vous avez:shared_ptr
paramètre est construit par copie: ref count incrément atomique .std::move
leshared_ptr
paramètre dans le membre de données: le nombre de références ne change pas ! Vous ne faites que voler des pointeurs / état: aucune opération coûteuse de comptage de références atomiques n'est impliquée.shared_ptr
paramètre est détruit; mais comme vous vous êtes déplacé à l'étape 2, il n'y a rien à détruire, car leshared_ptr
paramètre ne pointe plus vers rien. Encore une fois, aucun décrément atomique ne se produit dans ce cas.Bottom line: dans ce cas, vous obtenez un seul incrément atomique de ref count, c'est-à-dire une seule opération atomique .
Comme vous pouvez le voir, c'est bien mieux que deux incréments atomiques plus un décrément atomique (pour un total de trois opérations atomiques) pour le cas de copie.
la source
compilerInstance.setInvocation(std::move(sp));
n'y aura pas d' incrément . Vous pouvez obtenir le même comportement en ajoutant une surcharge qui prend unshared_ptr<>&&
mais pourquoi se dupliquer lorsque vous n'en avez pas besoin.setInvocation(new CompilerInvocation)
ou comme ratchet mentionnésetInvocation(std::move(sp))
. Désolé si mon premier commentaire n'était pas clair, je l'ai en fait posté par accident, avant d'avoir fini d'écrire, et j'ai décidé de le laisserLa copie d'un
shared_ptr
implique la copie de son pointeur d'objet d'état interne et la modification du nombre de références. Le déplacer implique uniquement d'échanger des pointeurs vers le compteur de référence interne et l'objet possédé, donc c'est plus rapide.la source
Il y a deux raisons d'utiliser std :: move dans cette situation. La plupart des réponses abordaient la question de la vitesse, mais ignoraient l'importante question de montrer plus clairement l'intention du code.
Pour un std :: shared_ptr, std :: move dénote sans ambiguïté un transfert de propriété du pointé, tandis qu'une simple opération de copie ajoute un propriétaire supplémentaire. Bien sûr, si le propriétaire d'origine renonce par la suite à sa propriété (par exemple en autorisant la destruction de son std :: shared_ptr), alors un transfert de propriété a été effectué.
Lorsque vous transférez la propriété avec std :: move, ce qui se passe est évident. Si vous utilisez une copie normale, il n'est pas évident que l'opération envisagée soit un transfert tant que vous n'avez pas vérifié que le propriétaire d'origine renonce immédiatement à la propriété. En prime, une mise en œuvre plus efficace est possible, car un transfert atomique de propriété peut éviter l'état temporaire où le nombre de propriétaires a augmenté de un (et les changements de référence qui en découlent).
la source
Au moins avec libstdc ++, vous devriez obtenir les mêmes performances avec déplacement et affectation car les
operator=
appelsstd::move
sur le pointeur entrant. Voir: https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/shared_ptr.h#L384la source