41 votes

Existe-t-il des cas d'utilisation valables pour utiliser new et delete, les pointeurs bruts ou les tableaux de style c avec le C++ moderne ?

Voici un exemple notable vidéo ( Arrêtez d'enseigner le C ) sur ce changement de paradigme à prendre dans l'enseignement du langage C++.

Et un article de blog également remarquable

J'ai un rêve...

Je rêve que les soi-disant cours/classes/curriculums C++ cessent d'enseigner (d'exiger) à leurs étudiants d'utiliser : ...

Depuis que le C++11 est devenu une norme établie, nous avons le Gestion dynamique de la mémoire installations aka pointeurs intelligents .
Même à partir de normes antérieures, nous avons la norme c++. Bibliothèque de conteneurs comme un bon remplacement pour les tableaux bruts (alloués avec des new T[] ) (notamment l'utilisation de std::string au lieu du style c NUL des tableaux de caractères terminés).

Question(s) en en gras :

Laissez de côté le placement new outrepasser, Existe-t-il un cas d'utilisation valable qui ne peut pas être réalisé en utilisant des pointeurs intelligents ou des conteneurs standard, mais uniquement en utilisant les éléments suivants new y delete directement (outre la mise en œuvre de ces classes de conteneurs/pointeurs intelligents, bien sûr) ?

C'est parfois rumeur : (comme ici o ici ) qu'en utilisant new y delete le laminé à la main peut être "plus efficace" pour certains cas. Lesquelles sont en fait ? Ces cas limites ne doivent-ils pas assurer le suivi des allocations de la même manière que les conteneurs standard ou les pointeurs intelligents ?

Presque la même chose pour les tableaux de taille fixe de style c brut : Il y a std::array de nos jours, ce qui permet de réaliser toutes sortes de travaux, de copies, de références, etc. de manière simple et syntaxiquement cohérente, comme l'attend tout le monde. Y a-t-il des cas d'utilisation pour choisir un T myArray[N]; de style c, de préférence à std::array<T,N> myArray; ?


En ce qui concerne l'interaction avec les bibliothèques de tiers :

En supposant qu'une bibliothèque tierce renvoie des pointeurs bruts alloués avec new comme

MyType* LibApi::CreateNewType() {
    return new MyType(someParams);
}

vous pouvez toujours l'envelopper dans un pointeur intelligent pour vous assurer que delete s'appelle :

std::unique_ptr<MyType> foo = LibApi::CreateNewType();

même si l'API exige que vous appeliez leur ancienne fonction pour libérer la ressource, par exemple

void LibApi::FreeMyType(MyType* foo);

vous pouvez toujours fournir une fonction de suppression :

std::unique_ptr<MyType, LibApi::FreeMyType> foo = LibApi::CreateNewType();

Je suis particulièrement intéressé par les documents valides "tous les jours" par opposition aux objectif académique/éducatif des exigences et des restrictions qui ne sont pas couvertes par les installations standard mentionnées.
Ce new y delete peut être utilisé dans des cadres de gestion de la mémoire / garbage collector ou la mise en œuvre de conteneurs standard est hors de question. 1 .


Une motivation majeure ...

... poser cette question est de donner une approche alternative à toutes les questions (devoirs), qui sont limitées à l'utilisation de l'une des constructions mentionnées dans le titre, mais des questions sérieuses sur le code prêt pour la production.

On les appelle souvent les notions de base de la gestion de la mémoire, qui est, selon l'OMI, manifestement erronée/mal comprise comme convenant pour débutants les conférences et les tâches.


1) <strong>Ajouter.. : </strong>En ce qui concerne ce paragraphe, cela devrait indiquer clairement que <code>new</code> y <code>delete</code> n'est pas destiné aux étudiants débutants en c++, mais devrait être laissé pour les cours plus avancés.

27voto

Yakk Points 31636

Quand la propriété ne doit pas être locale.

Par exemple, un conteneur de pointeurs peut ne pas vouloir que la propriété des pointeurs qu'il contient réside dans les pointeurs eux-mêmes. Si vous essayez d'écrire une liste liée avec des ptrs uniques en avant, au moment de la destruction, vous pouvez facilement faire sauter la pile.

A vector Un conteneur de pointeurs propriétaires comme celui-ci peut être mieux adapté pour stocker l'opération de suppression au niveau du conteneur ou du sous-conteneur, et non au niveau de l'élément.

Dans ce cas et dans d'autres cas similaires, vous enveloppez la propriété comme le fait un pointeur intelligent, mais vous le faites à un niveau supérieur. De nombreuses structures de données (graphes, etc.) peuvent avoir des problèmes similaires, où la propriété réside à un niveau supérieur à celui des pointeurs, et elles peuvent ne pas correspondre directement à un concept de conteneur existant.

Dans certains cas, il peut être facile de séparer la propriété du conteneur du reste de la structure de données. Dans d'autres, ce n'est pas le cas.

Parfois, vous avez des durées de vie non locales, non référencées, follement complexes. Il n'y a pas d'endroit sain pour placer le pointeur de propriété dans ces cas-là.

Déterminer l'exactitude ici est difficile, mais pas impossible. Il existe des programmes qui sont corrects et qui ont une sémantique de propriété aussi complexe.


Tous ces cas sont des cas particuliers, et peu de programmeurs devraient les rencontrer plus d'une poignée de fois dans leur carrière.

15voto

Jerry Coffin Points 237758

Je vais être anticonformiste et dire "non" (du moins à la question que vous vouliez vraiment poser, pour la plupart des cas qui ont été cités).

Ce qui semble être des cas d'utilisation évidents pour utiliser new y delete (par exemple, la mémoire brute pour un tas GC, le stockage pour un conteneur) ne le sont pas vraiment. Dans ces cas, vous voulez un stockage "brut", pas un objet (ou un tableau d'objets, ce qui est ce que l'on appelle la "mémoire brute"). new y new[] fournissent respectivement).

Puisque vous voulez un stockage brut, vous devez/veuillez vraiment utiliser operator new y operator delete pour gérer le stockage brut lui-même. Vous utilisez ensuite le placement new pour créer des objets dans ce stockage brut, et invoquer directement le destructeur pour détruire les objets. Selon la situation, il est possible d'utiliser un niveau d'indirection. Par exemple, les conteneurs de la bibliothèque standard utilisent une classe Allocator pour gérer ces tâches. Celle-ci est transmise en tant que paramètre de modèle, ce qui fournit un point de personnalisation (par exemple, un moyen d'optimiser l'allocation en fonction du modèle d'utilisation typique d'un conteneur particulier).

Donc, pour ces situations, vous finissez par utiliser la fonction new (à la fois dans le nouveau placement et dans l'invocation de la fonction operator new ), mais pas quelque chose comme T *t = new T[N]; ce qui est, j'en suis sûr, le sujet de votre question.

11voto

Jesper Juhl Points 14756

Un cas d'utilisation valable est celui de l'interaction avec le code existant. Surtout si l'on passe des pointeurs bruts à des fonctions qui en prennent possession.

Toutes les bibliothèques que vous utilisez n'utilisent pas forcément des pointeurs intelligents et pour les utiliser, vous devrez peut-être fournir ou accepter des pointeurs bruts et gérer leur durée de vie manuellement. Cela peut même être le cas dans votre propre base de code si elle a une longue histoire.

Un autre cas d'utilisation est de devoir interagir avec le C qui ne dispose pas de pointeurs intelligents.

5voto

Nikos C. Points 18676

Certaines API peuvent s'attendre à ce que vous créez des objets avec new mais reprendra la propriété de l'objet. Le site Qt par exemple, a un modèle parent-enfant où le parent supprime ses enfants. Si vous utilisez un pointeur intelligent, vous allez rencontrer des problèmes de double suppression si vous ne faites pas attention.

Exemple :

{
    // parentWidget has no parent.
    QWidget parentWidget(nullptr);

    // childWidget is created with parentWidget as parent.
    auto childWidget = new QWidget(&parentWidget);
}
// At this point, parentWidget is destroyed and it deletes childWidget
// automatically.

Dans cet exemple particulier, vous pouvez toujours utiliser un pointeur intelligent et tout ira bien :

{
    QWidget parentWidget(nullptr);
    auto childWidget = std::make_unique<QWidget>(&parentWidget);
}

car les objets sont détruits dans l'ordre inverse de leur déclaration. unique_ptr supprimera childWidget d'abord, ce qui rendra childWidget se désinscrire de parentWidget et éviter ainsi la double suppression. Cependant, la plupart du temps, vous n'avez pas cette netteté. Il existe de nombreuses situations où le parent sera détruit en premier, et dans ces cas, les enfants seront supprimés deux fois.

Dans le cas ci-dessus, nous possédons le parent dans cette portée, et avons donc le plein contrôle de la situation. Dans d'autres cas, le parent n'est pas forcément présent, mais nous transmettons la propriété de notre widget enfant à ce parent, qui vit ailleurs.

Vous pensez peut-être que pour résoudre ce problème, il suffit d'éviter le modèle parent-enfant et de créer tous vos widgets sur la pile et sans parent :

QWidget childWidget(nullptr);

ou avec un pointeur intelligent et sans parent :

auto childWidget = std::make_unique<QWidget>(nullptr);

Toutefois, cela peut aussi vous jouer des tours, car une fois que vous commencez à utiliser le widget, il risque de faire l'objet d'un nouveau brevet dans votre dos. Une fois qu'un autre objet devient le parent, vous obtenez une double suppression lorsque vous utilisez l'option unique_ptr et la suppression de la pile lors de sa création sur la pile.

La façon la plus simple de travailler avec ceci est d'utiliser new . Toute autre solution est une invitation à des problèmes, à plus de travail, ou aux deux.

De telles API se trouvent dans des logiciels modernes et non dépréciés (comme Qt) et ont été développées il y a des années, bien avant que les pointeurs intelligents ne soient une chose. Elles ne peuvent pas être modifiées facilement car cela casserait le code existant des gens.

4voto

darune Points 3515

Le PO demande spécifiquement comment/quand le handrolling sera plus efficace dans un cas d'utilisation quotidienne - et je vais répondre à cette question.

Dans l'hypothèse d'un compilateur/stl/plateforme moderne, il n'y a pas d'outil de gestion de la qualité. utilisation quotidienne où l'utilisation manuelle du nouveau et de la suppression sera plus efficace. Pour le cas du shared_ptr, je pense que ce sera marginal. Dans une (des) boucle(s) extrêmement serrée(s), il pourrait y avoir quelque chose à gagner en utilisant simplement le new brut pour éviter le comptage des ref (et trouver une autre méthode de nettoyage - à moins que cela ne vous soit imposé d'une manière ou d'une autre, vous avez choisi d'utiliser le shared_ptr pour une raison précise), mais c'est un peu plus compliqué que cela. no un exemple quotidien ou courant. Pour l'unique_ptr, il n'y a pas vraiment de différence, donc je pense que l'on peut dire qu'il s'agit plutôt de rumeurs et de folklore et que, du point de vue des performances, cela n'aura aucune importance (la différence ne sera pas mesurable dans les cas normaux).

Il existe des cas où il n'est pas souhaitable ou possible d'utiliser une classe de pointeurs intelligents, comme cela a déjà été couvert par d'autres.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X