Est-ce que new et delete sont toujours utiles en C ++ 14?

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:

  • un pool de mémoire
  • un allocateur
  • une variante balisée
  • messages binarys vers un tampon

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(new T(...)) 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.