Compte tenu de la disponibilité de make_unique
et de make_shared
, ainsi que de la suppression automatique par les destructeurs unique_ptr
et shared_ptr
, quelles sont les situations (hormis la prise en charge du code hérité) pour utiliser new
et delete
dans C ++ 14?
Bien que les pointeurs intelligents soient préférables aux pointeurs bruts dans de nombreux cas, il y a encore beaucoup de cas d’utilisation pour le new
/ delete
dans C ++ 14.
Si vous devez écrire quelque chose qui nécessite une construction sur place, par exemple:
vous devrez utiliser le placement new
et, éventuellement, delete
. Pas moyen de contourner cela.
Pour certains conteneurs que vous souhaitez écrire, vous pouvez utiliser des pointeurs bruts pour le stockage.
Même pour les pointeurs intelligents standard, vous aurez toujours besoin de new
si vous souhaitez utiliser des parameters de suppression personnalisés, car make_unique
et make_shared
ne le permettent pas.
C’est un choix relativement courant d’utiliser make_unique
et make_shared
plutôt que des appels bruts à new
. Ce n’est cependant pas obligatoire. En supposant que vous choisissiez de suivre cette convention, il y a quelques endroits à utiliser new
.
Premièrement, le placement non personnalisé new
(je vais négliger la partie “non-personnalisée”, et l’appeler simplement new
placement) est un jeu de cartes complètement différent de la norme (non-placement). Il est associé logiquement à l’appel manuel d’un destructeur. Standard new
acquiert à la fois une ressource du free store et y construit un object. Il est associé à delete
, ce qui détruit l’object et recycle le stockage dans le free store. Dans un sens, le placement de new
appels standard est interne et la delete
standard appelle le destructeur en interne.
Le placement new
correspond à la manière dont vous appelez directement un constructeur sur un certain stockage et est requirejs pour un code de gestion avancé de la durée de vie. Si vous implémentez optional
, une union
sûre de type sur le stockage aligné ou un pointeur intelligent (avec un stockage unifié et une durée de vie non unifiée, comme make_shared
), vous utiliserez le placement new
. Ensuite, à la fin de la durée de vie d’un object particulier, vous appelez directement son destructeur. À l’instar des new
emplacements et des delete
, les appels de destructeurs et les appels de destructeurs manuels se présentent par paires.
Le placement personnalisé new
est une autre raison d’utiliser new
. L’emplacement personnalisé new
peut être utilisé pour allouer des ressources à partir d’une allocation non globale du pool ou d’une allocation dans une page de mémoire partagée inter-processus, une allocation dans une mémoire partagée de carte vidéo, etc. Si vous voulez écrire make_unique_from_custom
qui alloue sa mémoire en utilisant un nouveau placement personnalisé, vous devrez utiliser le new
mot-clé. L’emplacement personnalisé new
peut agir comme un placement new (en ce sens qu’il n’acquiert pas réellement de ressources, mais que la ressource est transmise d’une manière ou d’une autre) ou peut agir comme un new
standard (en acquérant des ressources en utilisant les arguments transmis) .
La delete
placement personnalisé est appelée si un placement personnalisé lance de new
lancements, vous devrez peut-être l’écrire. En C ++, vous n’appelez pas la delete
placement personnalisée, elle (C ++) vous appelle (r surcharge) .
Enfin, make_shared
et make_unique
sont des fonctions incomplètes dans la mesure où elles ne prennent pas en charge les parameters de suppression personnalisés.
Si vous écrivez make_unique_with_deleter
, vous pouvez toujours utiliser make_unique
pour allouer les données et .release()
pour le soin de votre unique avec deleter. Si votre auteur veut placer son état dans le tampon pointé plutôt que dans le unique_ptr
ou dans une allocation distincte, vous devrez utiliser le new
emplacement ici.
Pour make_shared
, le code client n’a pas access au code de création “stub de comptage de référence”. Autant que je sache, vous ne pouvez pas facilement avoir à la fois “l’atsortingbution combinée d’un bloc de comptage d’object et de référence” et un deleter personnalisé.
En outre, make_shared
provoque la make_shared
l’allocation de la ressource (le stockage) pour que l’object lui-même persiste aussi longtemps que le weak_ptr
persiste: dans certains cas, cela peut ne pas être souhaitable. shared_ptr
pour éviter cela.
Dans les rares cas où vous souhaitez appeler new
-placement non-placement, vous pouvez appeler make_unique
, puis .release()
le pointeur si vous souhaitez gérer séparément cet unique_ptr
. Cela augmente votre couverture de ressources RAII et signifie que s’il y a des exceptions ou d’autres erreurs de logique, vous risquez moins de fuir.
J’ai noté ci-dessus que je ne savais pas comment utiliser un deleter personnalisé avec un pointeur partagé qui utilise facilement un seul bloc d’allocation. Voici une esquisse de la façon de le faire avec difficulté:
template struct custom_delete { std::tuple< std::aligned_storage< sizeof(T), alignof(T) >, D, bool > data; bool bCreated() const { return std::get<2>(data); } void markAsCreated() { std::get<2>()=true; } D&& d()&& { return std::get<1>(std::move(data)); } void* buff() { return &std::get<0>(data); } T* t() { return static_cast(static_cast(buff())); } template explicit custom_delete(Ts...&&ts):data( {},D(std::forward(ts)...),false ){} custom_delete(custom_delete&&)=default; ~custom_delete() { if (bCreated()) std::move(*this).d()(t()); } }; template> std::shared_ptr make_shared_with_deleter( D&& d, Ts&&... ts ) { auto internal = std::make_shared>(std::forward(d)); if (!internal) return {}; T* r = new(internal->data.buff()) T(std::forward(ts...)); internal->markAsCreated(); return { internal, r }; }
Je pense que cela devrait le faire. J’ai essayé de permettre aux personnes sans état de ne pas utiliser d’espace en utilisant un tuple
, mais j’ai peut-être foiré.
Dans une solution de qualité bibliothèque, si T::T(Ts...)
est noexcept
, je pourrais supprimer la surcharge bCreated
, car il ne serait pas possible pour une custom_delete
d’être détruite avant que le T
soit construit.
La seule raison pour laquelle je peux penser est que, de temps en temps, vous pouvez souhaiter utiliser un paramètre personnalisé avec votre unique_ptr
ou shared_ptr
. Pour utiliser un paramètre personnalisé, vous devez créer le pointeur intelligent directement, en transmettant le résultat de new
. Même cela n’est pas fréquent mais cela se produit dans la pratique.
À part cela, il semble que make_shared
/ make_unique
devrait couvrir à peu près tous les usages.
Je dirais que la seule raison d’être new
et de delete
est d’implémenter d’autres types de pointeurs intelligents.
Par exemple, la bibliothèque n’a toujours pas de pointeurs intrusifs comme boost :: intrusive_ptr, ce qui est dommage car ils sont supérieurs pour des raisons de performances aux pointeurs partagés, comme le fait remarquer Andrei Alexandrescu.