La différence est std::make_sharedqu'effectue une allocation de segment, alors que l'appel du std::shared_ptrconstructeur en effectue deux.
Où les allocations de tas se produisent-elles?
std::shared_ptr gère deux entités:
- le bloc de contrôle (stocke les métadonnées telles que les comptages de ref, le deleter effacé par type, etc.)
- l'objet géré
std::make_sharedeffectue 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 le std::shared_ptrconstructeur 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,
void F(const std::shared_ptr<Lhs> &lhs, const std::shared_ptr<Rhs> &rhs) { /* ... */ }
F(std::shared_ptr<Lhs>(new Lhs("foo")),
std::shared_ptr<Rhs>(new Rhs("bar")));
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 Rhsconstructeur 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_ptrimmé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.
auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
F(lhs, rhs);
La façon préférée de résoudre ce problème est bien sûr d'utiliser à la std::make_sharedplace.
F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));
Mise à jour II: Inconvénient de std::make_shared
Citant les commentaires de Casey :
Puisqu'il n'y a qu'une seule allocation, la mémoire de la pointe ne peut pas être désallouée tant que le bloc de contrôle n'est plus utilisé. A weak_ptrpeut garder le bloc de contrôle en vie indéfiniment.
Pourquoi les instances de weak_ptrs maintiennent-elles le bloc de contrôle en vie?
Il doit exister un moyen pour weak_ptrs de déterminer si l'objet géré est toujours valide (par exemple pour lock). Ils le font en vérifiant le nombre de shared_ptrs 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 le shared_ptrcompte et leweak_ptr compte atteignent tous les deux 0.
Retour à std::make_shared
Puisque std::make_sharedfait 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 de shared_ptrs ou de weak_ptrs vivants.
Supposons que nous ayons plutôt effectué deux allocations de tas pour le bloc de contrôle et l'objet géré via newet shared_ptrconstructeur. Ensuite, nous libérons la mémoire de l'objet géré (peut-être plus tôt) lorsqu'il n'y a pas de shared_ptrs vivants, et nous libérons la mémoire du bloc de contrôle (peut-être plus tard) lorsqu'il n'y a pas de weak_ptrs vivants.