J'ai regardé à travers le code source Clang et j'ai trouvé cet extrait:
void CompilerInstance::setInvocation(
std::shared_ptr<CompilerInvocation> Value) {
Invocation = std::move(Value);
}
Pourquoi voudrais-je std::move
un std::shared_ptr
?
Y at-il un point de transfert de propriété sur une ressource partagée?
Pourquoi ne ferais-je pas cela à la place?
void CompilerInstance::setInvocation(
std::shared_ptr<CompilerInvocation> Value) {
Invocation = Value;
}
Je pense que l'une des choses sur lesquelles les autres réponses n'ont pas suffisamment insisté est le point de vitesse .
std::shared_ptr
le compte de références est atomique . augmenter ou diminuer le nombre de références nécessite atomique incrémenter ou décrémenter. C'est cent fois plus lent que non-atomique incrémenté/décrémenté, pas de mentionner que si nous incrémentons et décrémentons le même compteur, nous nous retrouvons avec le nombre exact, ce qui nous fait perdre une tonne de temps et de ressources.
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 nombre de références n'est pas atomique , et il est cent fois plus rapide que de copier le shared_ptr
(et de causer atomique référence incrémente ou décrémente).
Notez que cette technique est utilisée uniquement à des fins d'optimisation. sa copie (comme vous l'avez suggéré) est tout aussi précise sur le plan des fonctionnalités.
En utilisant move
, vous évitez d'augmenter puis de diminuer immédiatement le nombre d'actions. Cela pourrait vous éviter des opérations atomiques coûteuses sur le nombre d'utilisations.
Move (comme constructeur de déplacement) pour _std::shared_ptr
_ sont cheap , comme ils sont fondamentalement "voler des pointeurs" (de la source à la destination; pour être plus précis, le bloc de contrôle d'état entier est "volé" de la source à la destination, y compris les informations de décompte de références).
Au lieu de cela copie opérations sur _std::shared_ptr
_ invoke atomic augmentation du nombre de références (c'est-à-dire pas uniquement _++RefCount
_ sur un entier RefCount
membre de données, mais appelant par exemple InterlockedIncrement
sous Windows), ce qui est plus cher que le simple vol de pointeurs/d'état.
Nous analysons donc en détail la dynamique du nombre de références de ce cas:
_// shared_ptr<CompilerInvocation> sp;
compilerInstance.setInvocation(sp);
_
Si vous passez sp
par valeur et que vous prenez ensuite un copy dans la méthode _CompilerInstance::setInvocation
_, vous avez:
shared_ptr
_ est construit en fonction de la copie: ref count atomic increment.shared_ptr
_ dans le membre de données: ref count atomic incrémenter.shared_ptr
_ est détruit: ref count atomic décrémenter.Vous avez deux incréments atomiques et un décrément atomique, pour un total de trois atomique opérations.
Au lieu de cela, si vous passez le paramètre _shared_ptr
_ par valeur, puis std::move
dans la méthode (comme cela a été fait correctement dans le code de Clang), vous avez:
shared_ptr
_ est construit en fonction de la copie: ref count atomic increment.std::move
_ le paramètre _shared_ptr
_ dans le membre de données: le nombre de références fait not change! Vous ne faites que voler des pointeurs/états: aucune opération de décompte atomique coûteuse n'est impliquée.shared_ptr
_ est détruit; mais depuis que vous vous êtes déplacé à l'étape 2, il n'y a rien à détruire, car le paramètre _shared_ptr
_ ne pointe plus rien. Encore une fois, aucune décrémentation atomique ne se produit dans ce cas.Ligne de fond: dans ce cas, vous obtenez juste one ref compte l'incrément atomique, c'est-à-dire juste n atomique opération.
Comme vous pouvez le constater, c’est beaucoup 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 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 l'échange de pointeurs sur le compteur de référence interne et sur l'objet en propriété, ce qui accélère le processus.
Il y a deux raisons d'utiliser std :: move dans cette situation. La plupart des réponses traitaient de la rapidité, mais ne tenaient pas compte de l’importance 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 pointee, tandis qu'une simple opération de copie ajoute un propriétaire supplémentaire. Bien sûr, si le propriétaire initial abandonne par la suite sa propriété (par exemple, en permettant à son std :: shared_ptr d'être détruit), un transfert de propriété est alors 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 en a immédiatement abandonné la propriété. En prime, une mise en œuvre plus efficace est possible, puisqu'un transfert de propriété atomique peut éviter l'état temporaire dans lequel le nombre de propriétaires a augmenté d'un (et les changements correspondants dans les comptages de références).