web-dev-qa-db-fra.com

Pourquoi devrais-je std :: move an std :: shared_ptr?

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;
}
126
sdgfsdh

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.

108
David Haim

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.

115
Bo Persson

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:

  1. Lors de la saisie de la méthode, le paramètre _shared_ptr_ est construit en fonction de la copie: ref count atomic increment.
  2. Dans le corps de la méthode, vous copie le paramètre _shared_ptr_ dans le membre de données: ref count atomic incrémenter.
  3. Lorsque vous quittez la méthode, le paramètre _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:

  1. Lors de la saisie de la méthode, le paramètre _shared_ptr_ est construit en fonction de la copie: ref count atomic increment.
  2. Dans le corps de la méthode, vous _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.
  3. Lorsque vous quittez la méthode, le paramètre _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.

57
Mr.C64

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.

20
SingerOfTheFall

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).

14
Stephen C. Steel