Quand une fonction prend un shared_ptr
(de boost ou C ++ 11 STL), le passez-vous:
par référence const:
void foo(const shared_ptr<T>& p)
ou par valeur
void foo(shared_ptr<T> p)
:?
Je préférerais la première méthode car je pense qu'elle serait plus rapide. Mais cela en vaut-il vraiment la peine ou y a-t-il des problèmes supplémentaires?
Pourriez-vous s'il vous plaît donner les raisons de votre choix ou le cas échéant, pourquoi vous pensez que cela n'a pas d'importance.
c++
c++11
boost
shared-ptr
Danvil
la source
la source
shared_ptr
, et je peux le changer si je veux.», Tandis que la version de valeur dit «Je vais copier votreshared_ptr
, donc pendant que je peux le changer, vous ne le saurez jamais. ) Un paramètre const-reference est la vraie solution, qui dit "Je vais en alias certainsshared_ptr
, et je promets de ne pas le changer." (Ce qui est extrêmement similaire à la sémantique par valeur!)shared_ptr
membre de la classe. Le faites-vous par const-refs?Réponses:
Cette question a été discutée et répondue par Scott, Andrei et Herb lors de la session Ask Us Anything à C ++ and Beyond 2011 . Regardez à partir de 4:34 sur les
shared_ptr
performances et l'exactitude .En bref, il n'y a aucune raison de passer par la valeur, sauf si le but est de partager la propriété d'un objet (par exemple entre différentes structures de données ou entre différents threads).
À moins que vous ne puissiez le déplacer-l'optimiser comme expliqué par Scott Meyers dans la vidéo de discussion liée ci-dessus, mais cela est lié à la version réelle de C ++ que vous pouvez utiliser.
Une mise à jour majeure de cette discussion a eu lieu lors du panel interactif de la conférence GoingNative 2012 : Ask Us Anything! ce qui vaut le détour, surtout à partir de 22h50 .
la source
Value*
est court et lisible, mais c'est mauvais, alors maintenant mon code est pleinconst shared_ptr<Value>&
et il est nettement moins lisible et juste ... moins rangé. Ce qui était auparavantvoid Function(Value* v1, Value* v2, Value* v3)
est maintenantvoid Function(const shared_ptr<Value>& v1, const shared_ptr<Value>& v2, const shared_ptr<Value>& v3)
, et les gens sont d'accord avec ça?class Value {...}; using ValuePtr = std::shared_ptr<Value>;
Alors votre fonction devient plus simple:void Function(const ValuePtr& v1, const ValuePtr& v2, const ValuePtr& v3)
et vous obtenez des performances maximales. C'est pourquoi vous utilisez C ++, n'est-ce pas? :)Voici le point de vue d' Herb Sutter
la source
Personnellement, j'utiliserais une
const
référence. Il n'est pas nécessaire d'incrémenter le nombre de références juste pour le décrémenter à nouveau pour un appel de fonction.la source
shared_ptr
fonctionnement, le seul inconvénient possible à ne pas passer par référence est une légère perte de performances. Il y a deux causes ici. a) la fonction d'alias de pointeur signifie que des pointeurs d'une valeur de données plus un compteur (peut-être 2 pour les références faibles) sont copiés, il est donc légèrement plus cher de copier le tour de données. b) le comptage de référence atomique est légèrement plus lent que l'ancien code d'incrémentation / décrémentation ordinaire, mais il est nécessaire pour garantir la sécurité des threads. Au-delà de cela, les deux méthodes sont les mêmes pour la plupart des intentions et des fins.Passez par
const
référence, c'est plus rapide. Si vous avez besoin de le stocker, disons dans un conteneur, la réf. le compte sera automatiquement incrémenté par l'opération de copie.la source
J'ai exécuté le code ci-dessous, une fois en
foo
prenant leshared_ptr
byconst&
et encore une fois enfoo
prenant lashared_ptr
valeur by.Utilisation de VS2015, version x86, sur mon processeur Intel Core 2 Quad (2,4 GHz)
La version de copie par valeur était un ordre de grandeur plus lent.
Si vous appelez une fonction de manière synchrone à partir du thread actuel, préférez la
const&
version.la source
foo()
fonction ne devrait même pas accepter un pointeur partagé en premier lieu car elle n'utilise pas cet objet: elle doit accepter aint&
et dop = ++x;
, appelantfoo(*p);
depuismain()
. Une fonction accepte un objet pointeur intelligent lorsqu'elle doit en faire quelque chose, et la plupart du temps, ce que vous devez faire, c'est le déplacer (std::move()
) ailleurs, donc un paramètre de valeur n'a aucun coût.Depuis C ++ 11, vous devriez le prendre par valeur sur const & plus souvent que vous ne le pensez.
Si vous prenez le std :: shared_ptr (plutôt que le type sous-jacent T), vous le faites parce que vous voulez en faire quelque chose.
Si vous souhaitez le copier quelque part, il est plus logique de le prendre par copie, et std :: le déplacer en interne, plutôt que de le prendre par const & puis de le copier plus tard. Cela est dû au fait que vous autorisez l'appelant à tour à tour std :: déplacer le shared_ptr lors de l'appel de votre fonction, vous économisant ainsi un ensemble d'opérations d'incrémentation et de décrémentation. Ou pas. C'est-à-dire que l'appelant de la fonction peut décider s'il a besoin ou non du std :: shared_ptr après avoir appelé la fonction, et selon qu'il se déplace ou non. Ce n'est pas réalisable si vous passez par const &, et c'est donc de préférence de le prendre en valeur.
Bien sûr, si l'appelant a besoin de son shared_ptr plus longtemps (donc ne peut pas std :: le déplacer) et que vous ne voulez pas créer une copie simple dans la fonction (par exemple, vous voulez un pointeur faible, ou vous ne voulez que parfois pour le copier, selon certaines conditions), alors un const & peut être préférable.
Par exemple, vous devez faire
plus de
Parce que dans ce cas, vous créez toujours une copie en interne
la source
Ne sachant pas le coût en temps de l'opération de copie shared_copy où l'incrémentation et la décrémentation atomiques sont effectuées, j'ai souffert d'un problème d'utilisation du processeur beaucoup plus élevé. Je ne m'attendais jamais à ce que l'incrémentation et la décrémentation atomiques coûtent autant.
Suite à mon résultat de test, l'incrémentation et la décrémentation atomiques int32 prennent 2 ou 40 fois l'incrémentation et la décrémentation non atomiques. Je l'ai eu sur 3GHz Core i7 avec Windows 8.1. Le premier résultat sort quand aucune contention ne se produit, le second quand une forte possibilité de contention se produit. Je garde à l'esprit que les opérations atomiques sont enfin des verrous basés sur le matériel. La serrure est la serrure. Mauvais pour les performances en cas de conflit.
Dans ce cas, j'utilise toujours byref (const shared_ptr &) que byval (shared_ptr).
la source
Il y a eu un récent article de blog: https://medium.com/@vgasparyan1995/pass-by-value-vs-pass-by-reference-to-const-c-f8944171e3ce
La réponse est donc la suivante: ne passez (presque) jamais
const shared_ptr<T>&
.Passez simplement la classe sous-jacente à la place.
Fondamentalement, les seuls types de paramètres raisonnables sont:
shared_ptr<T>
- Modifier et s'appropriershared_ptr<const T>
- Ne modifiez pas, prenez possessionT&
- Modifier, pas de propriétéconst T&
- Ne modifiez pas, pas de propriétéT
- Ne pas modifier, pas de propriété, pas cher à copierComme @accel l'a souligné dans https://stackoverflow.com/a/26197326/1930508, les conseils de Herb Sutter sont les suivants:
Mais dans combien de cas n'êtes-vous pas sûr? C'est donc une situation rare
la source
Il est connu que le passage de shared_ptr par valeur a un coût et doit être évité si possible.
Le coût du passage par shared_ptr
La plupart du temps, passer shared_ptr par référence, et encore mieux par référence const, ferait l'affaire.
La directive de base cpp a une règle spécifique pour passer shared_ptr
R.34: Prendre un paramètre shared_ptr pour exprimer qu'une fonction est propriétaire de la partie
Un exemple de passage de shared_ptr par valeur est vraiment nécessaire lorsque l'appelant transmet un objet partagé à un appelant asynchrone - c'est-à-dire que l'appelant sort de la portée avant que l'appelé termine son travail. L'appelé doit "étendre" la durée de vie de l'objet partagé en prenant un share_ptr par valeur. Dans ce cas, passer une référence à shared_ptr ne fera pas l'affaire.
Il en va de même pour passer un objet partagé à un thread de travail.
la source
shared_ptr n'est pas assez grand, et son constructeur \ destructeur ne fait pas assez de travail pour qu'il y ait suffisamment de surcharge de la copie pour se soucier des performances de passage par référence vs de passage par copie.
la source
shared_ptr<int>
valeur by prend plus de 100 instructions x86 (y compris leslock
instructions ed coûteuses pour augmenter / diminuer atomiquement le nombre de références). Passer par ref constante revient à passer un pointeur vers n'importe quoi (et dans cet exemple sur l'explorateur du compilateur Godbolt, l'optimisation de l'appel de queue transforme cela en un jmp simple au lieu d'un appel: godbolt.org/g/TazMBU ).