std::shared_ptr<Object> p1 = std::make_shared<Object>("foo");
std::shared_ptr<Object> p2(new Object("foo"));
De nombreux articles sur Google et stackoverflow sont là-dessus, mais je ne peux pas comprendre pourquoi make_shared
est plus efficace que l'utilisation directe shared_ptr
.
Quelqu'un peut-il m'expliquer étape par étape la séquence d'objets créés et les opérations effectuées par les deux afin que je puisse comprendre comment make_shared
est efficace. J'ai donné un exemple ci-dessus pour référence.
c++
c++11
shared-ptr
Anup Buchke
la source
la source
make_shared
vous pouvez écrireauto p1(std::make_shared<A>())
et p1 aura le type correct.Réponses:
La différence est
std::make_shared
qu'effectue une allocation de segment, alors que l'appel dustd::shared_ptr
constructeur en effectue deux.Où les allocations de tas se produisent-elles?
std::shared_ptr
gère deux entités:std::make_shared
effectue une allocation de segment unique tenant compte de l'espace nécessaire à la fois au bloc de contrôle et aux données. Dans l'autre cas,new Obj("foo")
appelle une allocation de segment pour les données gérées et lestd::shared_ptr
constructeur en effectue une autre pour le bloc de contrôle.Pour plus d'informations, consultez les notes d'implémentation sur cppreference .
Mise à jour I: exception-sécurité
REMARQUE (2019/08/30) : Ce n'est pas un problème depuis C ++ 17, en raison des changements dans l'ordre d'évaluation des arguments de fonction. Plus précisément, chaque argument d'une fonction doit être entièrement exécuté avant l'évaluation des autres arguments.
Étant donné que l'OP semble s'interroger sur le côté exception-sécurité des choses, j'ai mis à jour ma réponse.
Considérez cet exemple,
Parce que C ++ permet un ordre arbitraire d'évaluation des sous-expressions, un ordre possible est:
new Lhs("foo"))
new Rhs("bar"))
std::shared_ptr<Lhs>
std::shared_ptr<Rhs>
Supposons maintenant que nous obtenons une exception levée à l'étape 2 (par exemple, exception de mémoire insuffisante, le
Rhs
constructeur a levé une exception). Nous perdons ensuite la mémoire allouée à l'étape 1, car rien n'aura eu l'occasion de le nettoyer. Le cœur du problème ici est que le pointeur brut n'a pas étéstd::shared_ptr
immédiatement transmis au constructeur.Une façon de résoudre ce problème est de les faire sur des lignes distinctes afin que cette ordonnance arbitraire ne puisse pas se produire.
La façon préférée de résoudre ce problème est bien sûr d'utiliser à la
std::make_shared
place.Mise à jour II: Inconvénient de
std::make_shared
Citant les commentaires de Casey :
Pourquoi les instances de
weak_ptr
s maintiennent-elles le bloc de contrôle en vie?Il doit exister un moyen pour
weak_ptr
s de déterminer si l'objet géré est toujours valide (par exemple pourlock
). Ils le font en vérifiant le nombre deshared_ptr
s qui possèdent l'objet géré, qui est stocké dans le bloc de contrôle. Le résultat est que les blocs de contrôle sont vivants jusqu'à ce que leshared_ptr
compte et leweak_ptr
compte atteignent tous les deux 0.Retour à
std::make_shared
Puisque
std::make_shared
fait une seule allocation de segment pour le bloc de contrôle et l'objet géré, il n'y a aucun moyen de libérer la mémoire pour le bloc de contrôle et l'objet géré indépendamment. Nous devons attendre jusqu'à ce que nous puissions libérer à la fois le bloc de contrôle et l'objet géré, qui se trouve être jusqu'à ce qu'il n'y ait pas deshared_ptr
s ou deweak_ptr
s vivants.Supposons que nous ayons plutôt effectué deux allocations de tas pour le bloc de contrôle et l'objet géré via
new
etshared_ptr
constructeur. Ensuite, nous libérons la mémoire de l'objet géré (peut-être plus tôt) lorsqu'il n'y a pas deshared_ptr
s vivants, et nous libérons la mémoire du bloc de contrôle (peut-être plus tard) lorsqu'il n'y a pas deweak_ptr
s vivants.la source
make_shared
: puisqu'il n'y a qu'une seule allocation, la mémoire de la pointe ne peut pas être désallouée jusqu'à ce que le bloc de contrôle ne soit plus utilisé. Aweak_ptr
peut maintenir le bloc de contrôle en vie indéfiniment.make_shared
et de manièremake_unique
cohérente, vous n'aurez pas de pointeurs bruts et vous pourrez traiter chaque occurrencenew
comme une odeur de code.shared_ptr
, et pas deweak_ptr
s, l'appelreset()
à l'shared_ptr
instance supprimera le bloc de contrôle. Mais c'est indépendamment ou si amake_shared
été utilisé. L'utilisationmake_shared
fait une différence car elle peut prolonger la durée de vie de la mémoire allouée à l'objet géré . Lorsque leshared_ptr
nombre atteint 0, le destructeur de l'objet géré est appelé indépendammentmake_shared
, mais la libération de sa mémoire ne peut être effectuée que si ellemake_shared
n'a pas été utilisée. J'espère que cela le rendra plus clair.Le pointeur partagé gère à la fois l'objet lui-même et un petit objet contenant le nombre de références et d'autres données de gestion.
make_shared
peut allouer un seul bloc de mémoire pour contenir les deux; la construction d'un pointeur partagé à partir d'un pointeur vers un objet déjà alloué devra allouer un deuxième bloc pour stocker le nombre de références.En plus de cette efficacité, l'utilisation
make_shared
signifie que vous n'avez pas besoin de traiter avecnew
des pointeurs bruts, ce qui donne une meilleure sécurité d'exception - il n'y a aucune possibilité de lever une exception après avoir alloué l'objet mais avant de l'affecter au pointeur intelligent.la source
Il existe un autre cas où les deux possibilités diffèrent, en plus de celles déjà mentionnées: si vous avez besoin d'appeler un constructeur non public (protégé ou privé), make_shared pourrait ne pas être en mesure d'y accéder, tandis que la variante avec le nouveau fonctionne bien .
la source
new
j'ai décidé de l'utiliser , sinon j'aurais utilisémake_shared
. Voici une question connexe à ce sujet: stackoverflow.com/questions/8147027/… .Si vous avez besoin d'un alignement spécial de la mémoire sur l'objet contrôlé par shared_ptr, vous ne pouvez pas compter sur make_shared, mais je pense que c'est la seule bonne raison de ne pas l'utiliser.
la source
Je vois un problème avec std :: make_shared, il ne prend pas en charge les constructeurs privés / protégés
la source
Shared_ptr
: Effectue deux allocations de segmentsMake_shared
: Effectue une seule allocation de segment de mémoirela source
À propos de l'efficacité et du temps consacré à l'allocation, j'ai fait ce test simple ci-dessous, j'ai créé de nombreuses instances de ces deux façons (une à la fois):
Le fait est que l'utilisation de make_shared a pris le double de temps par rapport à l'utilisation de new. Donc, en utilisant new, il y a deux allocations de tas au lieu d'une utilisant make_shared. C'est peut-être un test stupide, mais cela ne montre-t-il pas que l'utilisation de make_shared prend plus de temps que l'utilisation de new? Bien sûr, je parle uniquement du temps utilisé.
la source
Je pense que la partie sécurité exceptionnelle de la réponse de m. Mpark est toujours une préoccupation valable. lors de la création d'un shared_ptr comme ceci: shared_ptr <T> (nouveau T), le nouveau T peut réussir, tandis que l'allocation de shared_ptr du bloc de contrôle peut échouer. dans ce scénario, le T nouvellement alloué fuira, car le shared_ptr n'a aucun moyen de savoir qu'il a été créé sur place et il est sûr de le supprimer. ou est-ce que je manque quelque chose? Je ne pense pas que les règles plus strictes sur l'évaluation des paramètres de fonction aident en aucune façon ici ...
la source