Est-ce que «nouveau» et «supprimer» deviennent obsolètes en C ++?

68

Je suis tombé sur un quiz impliquant une déclaration de tableau de différentes tailles. La première chose qui m'est venue à l'esprit est que je devrais utiliser l'allocation dynamique avec la newcommande, comme ceci:

while(T--) {
   int N;
   cin >> N;
   int *array = new int[N];
   // Do something with 'array'
   delete[] array;
}

Cependant, j'ai vu que l'une des solutions permettait le cas suivant:

while(T--) {
    int N;
    cin >> N;
    int array[N];
    // Do something with 'array'
}

Après un peu de recherche, j'ai lu que g ++ le permet, mais cela m'a fait réfléchir, dans quels cas est-il alors nécessaire d'utiliser l'allocation dynamique? Ou est-ce que le compilateur traduit cela comme une allocation dynamique?

La fonction de suppression est incluse. Notez cependant que la question ici ne concerne pas les fuites de mémoire.

learning_dude
la source
54
Le deuxième exemple utilise un tableau de longueur variable qui n'a jamais fait partie de C ++. Dans ce cas, utilisez std::vectorplutôt ( std::vector<int> array(N);).
Un programmeur du
7
La réponse directe à votre question devrait être: non, ce n'est pas obsolète. Même si les versions modernes de C ++ offrent de nombreuses fonctionnalités simplifiant la gestion de la propriété de la mémoire (pointeurs intelligents), il est toujours courant d'allouer des objets en appelant new OBJdirectement.
pptaszni
8
Pour d'autres personnes qui ne savent pas pourquoi les gens parlent de fuites de mémoire, la question a été modifiée pour corriger un bogue qui n'était pas significatif à la question
Mike Caron
4
@Mannoj préfère utiliser les termes dynamique et automatique pour empiler et empiler. C'est rare mais il est possible d'implémenter C ++ sans tas ni pile.
user4581301
1
Rien n'a jamais été déprécié en C ++ et rien ne le sera jamais. Cela fait partie de ce que signifie C ++.
JoelFan

Réponses:

114

Aucun extrait que vous montrez n'est un code C ++ moderne idiomatique.

newet delete(et new[]et delete[]) ne sont pas obsolètes en C ++ et ne le seront jamais. Ils sont toujours le moyen d'instancier des objets alloués dynamiquement. Cependant, comme vous devez toujours faire correspondre un newavec un delete(et un new[]avec un delete[]), il est préférable de les conserver dans des classes (bibliothèque) qui vous garantissent cela. Voir Pourquoi les programmeurs C ++ devraient-ils minimiser l'utilisation de «nouveau»? .

Votre premier extrait de code utilise un "nu" new[]et puis jamais delete[]le tableau créé. C'est un problème. std::vectorfait tout ce dont vous avez besoin ici très bien. Il utilisera une certaine forme de newcoulisses (je ne plongerai pas dans les détails d'implémentation), mais pour tout ce que vous avez à faire, c'est un tableau dynamique mais meilleur et plus sûr.

Votre deuxième extrait utilise des "tableaux de longueur variable" (VLA), une fonctionnalité C que certains compilateurs autorisent également en C ++ comme extension. Contrairement aux newVLA, ils sont essentiellement alloués sur la pile (une ressource très limitée). Mais plus important encore, ils ne sont pas une fonctionnalité C ++ standard et doivent être évités car ils ne sont pas portables. Ils ne remplacent certainement pas l'allocation dynamique (c'est-à-dire le tas).

Max Langhof
la source
3
Je voudrais ajouter que bien que les VLA ne soient pas officiellement dans la norme, ils sont pris en charge par tous les principaux compilateurs, et donc la décision de les éviter ou non est plus une question de style / préférence qu'une préoccupation réaliste de portabilité.
Stack Tracer
4
Rappelez-vous également que vous ne pouvez pas retourner un tableau ou le stocker ailleurs, donc VLA ne survivra jamais au temps d'exécution de la fonction
Ruslan
16
@StackTracer À ma connaissance, MSVC ne prend pas en charge les VLA. Et MSVC est très certainement un "compilateur majeur".
Max Langhof
2
"vous devez toujours faire correspondre un nouveau avec une suppression" - pas si vous travaillez avec Qt, car ses classes de base ont toutes des ramasse-miettes, vous devez donc simplement l'utiliser newet l'oublier, la plupart du temps. Pour les éléments de l'interface graphique, lorsque le widget parent est fermé, les enfants sortent de la portée et sont automatiquement récupérés.
vsz
6
@vsz Même dans Qt, chacun a newtoujours une correspondance delete; c'est juste que les deletes sont effectués par le widget parent plutôt que dans le même bloc de code que les news.
jjramsey
22

Eh bien, pour commencer, new/ deletene sont pas obsolètes.

Dans votre cas particulier, ce n'est pas la seule solution. Ce que vous choisissez dépend de ce qui a été caché sous votre commentaire "faire quelque chose avec le tableau".

Votre 2ème exemple utilise une extension VLA non standard qui essaie d'adapter le tableau sur la pile. Cela a certaines limites - à savoir une taille limitée et l'impossibilité d'utiliser cette mémoire après que le tableau soit hors de portée. Vous ne pouvez pas le déplacer, il "disparaîtra" après le déroulement de la pile.

Donc, si votre seul objectif est de faire un calcul local, puis de jeter les données, cela pourrait fonctionner correctement. Cependant, une approche plus robuste consisterait à allouer la mémoire dynamiquement, de préférence avec std::vector. De cette façon, vous avez la possibilité de créer de l'espace pour exactement autant d'éléments que vous en avez besoin en vous basant sur une valeur d'exécution (ce que nous recherchons tout au long), mais cela se nettoiera également bien et vous pourrez le déplacer de cette portée si vous souhaitez conserver la mémoire en cours d'utilisation pour plus tard.

En revenant au début, vous vector utiliserez probablement newquelques couches plus en profondeur, mais vous ne devriez pas vous en préoccuper, car l'interface qu'elle présente est bien supérieure. En ce sens, l'utilisation newet deletepeut être considérée comme découragée.

Bartek Banachewicz
la source
1
Notez les "... quelques couches plus profondes". Si vous deviez implémenter vos propres conteneurs, vous devriez toujours éviter d'utiliser newet delete, mais plutôt utiliser des pointeurs intelligents comme std::unique_pointer.
maximum
1
qui est en fait appeléstd::unique_ptr
user253751
2
@Max: std::unique_ptrle destructeur par défaut appelle deleteou delete[], ce qui signifie que l'objet possédé doit avoir été alloué par newou de new[]toute façon, dans quels appels sont cachés std::make_uniquedepuis C ++ 14.
Laurent LA RIZZA
15

Vos deuxièmes exemples utilisent des tableaux de longueur variable (VLA), qui sont en fait une fonctionnalité C99 ( pas C ++!), Mais néanmoins pris en charge par g ++ .

Voir aussi cette réponse .

Notez que les tableaux de longueur variable sont différents de new/ deleteet ne les "déconseillent" en aucune façon.

Sachez également que les VLA ne sont pas ISO C ++.

andreee
la source
13

Le C ++ moderne offre des moyens plus simples de travailler avec des allocations dynamiques. Les pointeurs intelligents peuvent prendre soin du nettoyage après les exceptions (qui peuvent se produire n'importe où si autorisé) et les retours précoces, dès que les structures de données référencées sortent du domaine, il peut donc être judicieux de les utiliser à la place:

  int size=100;

  // This construct requires the matching delete statement.
  auto buffer_old = new int[size];

  // These versions do not require `delete`:
  std::unique_ptr<int[]> buffer_new (new int[size]);
  std::shared_ptr<int[]> buffer_new (new int[size]); 
  std::vector<int> buffer_new (size);  int* raw_access = buffer_new.data();

A partir de C ++ 14, vous pouvez également écrire

auto buffer_new = std::make_unique<int[]>(size);

cela semble encore plus agréable et empêcherait une fuite de mémoire en cas d'échec de l'allocation. A partir de C ++ 20, vous devriez pouvoir faire autant que

auto a = std::make_shared<int[]>(size);

pour moi, cela ne se compile toujours pas au moment de l'écriture avec gcc 7.4.0. Dans ces deux exemples, nous utilisons également la autodéclaration de type à gauche. Dans tous les cas, utilisez le tableau comme d'habitude:

buffer_old[0] = buffer_new[0] = 17;

Les fuites de mémoire newet les plantages de doublés deletesont quelque chose que le C ++ a été critiqué pendant de nombreuses années, étant le "point central" de l'argumentation pour passer à d'autres langages. Peut-être préférable d'éviter.

Audrius Meskauskas
la source
Vous devez éviter les unique/shared_ptrconstructeurs en faveur de make_unique/shared, non seulement vous n'avez pas à écrire deux fois le type construit (en utilisant auto), mais vous ne risquez pas de fuir la mémoire ou les ressources si la construction échoue à mi-chemin (si vous utilisez un type qui peut échouer)
Simon Buchan
2
make_unique est disponible avec les tableaux de C ++ 14 et make_shared de C ++ 20 uniquement. C'est encore rarement un paramètre par défaut, donc proposer std :: make_shared <int []> (taille) m'a semblé quelque peu à l'avance.
Audrius Meskauskas
C'est suffisant! Je n'utilise pas vraiment make_shared<int[]>grand-chose, quand vous en avez presque toujours envie vector<int>, mais bon à savoir.
Simon Buchan
Pédanterie excessive, mais IIRC le unique_ptrconstructeur n'est pas jeter, donc pour ceux Tqui n'ont pas de constructeurs, il n'y a donc aucun risque de fuite avec unique_ptr(new int[size])et shared_ptra ce qui suit: "Si une exception est levée, supprimer p est appelé lorsque T n'est pas un type de tableau, supprimer [ ] p sinon. ", vous avez donc le même effet - le risque est pour unique/shared_ptr(new MyPossiblyAllocatingType[size]).
Simon Buchan
3

new et delete ne sont pas obsolètes.

Les objets créés par le nouvel opérateur peuvent être passés par référence. Les objets peuvent être supprimés à l'aide de la suppression.

nouveau et supprimer sont les aspects fondamentaux de la langue. La persistance d'un objet peut être gérée en utilisant new et delete. Ceux-ci ne seront certainement pas dépréciés.

L'instruction - int array [N] est un moyen de définir un tableau. Le tableau peut être utilisé dans le cadre du bloc de code englobant. Il ne peut pas être transmis comme la façon dont un objet est passé à une autre fonction.

Gopinath
la source
2

Le premier exemple a besoin d'un delete[]à la fin, ou vous aurez une fuite de mémoire.

Le deuxième exemple utilise une longueur de tableau variable qui n'est pas prise en charge par C ++; il autorise uniquement l'expression constante pour la longueur du tableau .

Dans ce cas, il est utile d'utiliser std::vector<>comme solution; qui encapsule toutes les actions que vous pouvez effectuer sur un tableau dans une classe de modèle.

rasoir zig
la source
3
Que voulez-vous dire par "jusqu'à C ++ 11"? Je suis presque sûr que les VLA ne sont jamais devenus partie intégrante de la norme.
churill
regardez la norme de c ++ 14 [norme c ++ 14] ( isocpp.org/files/papers/N3690.pdf ) à la page 184 paragraphe 8.3.4
rasoir zig
4
Cest pas la . Standard, mais seulement un projet et la partie sur les « tableaux d'exécution lié » n'a pas fait dans la norme pour autant que je peux dire cppreference ne mentionne pas Vlas.
churill
1
@zigrazor cppreference.com a une liste de liens vers les projets les plus proches avant / après la publication de chacune des normes. Les normes publiées ne sont pas disponibles gratuitement, mais ces versions devraient être très proches. Comme vous pouvez le voir sur les numéros de document, votre brouillon lié est un brouillon de travail plus ancien pour C ++ 14.
noyer
2
@learning_dude Il n'est pas pris en charge par les normes. La réponse est (maintenant) correcte (quoique brève). Cela ne fonctionne que pour vous car GCC l'autorise comme une extension non standard .
noyer
-4

La syntaxe ressemble à C ++, mais l'idiome est similaire à l'ancien Algol60 simple. Il était courant d'avoir des blocs de code comme celui-ci:

read n;
begin
    integer array x[1:n];
    ... 
end;

L'exemple pourrait s'écrire:

while(T--) {
    int N;
    cin >> N;
    {
        int array[N];
        // Do something with 'array'
    }
}

Cela me manque parfois dans les langues actuelles;)

kdo
la source