web-dev-qa-db-fra.com

Instanciation d'objets C ++

Je suis un programmeur C qui essaie de comprendre le C++. De nombreux tutoriels illustrent l'instanciation d'objets à l'aide d'un extrait de code tel que:

Dog* sparky = new Dog();

ce qui implique que plus tard vous ferez:

delete sparky;

ce qui a du sens. Maintenant, dans le cas où l’allocation dynamique de mémoire est inutile, n’y a-t-il aucune raison d’utiliser ce qui précède au lieu de

Dog sparky;

et laisser le destructeur être appelé une fois que sparky est hors de portée?

Merci!

111
el_champo

Au contraire, vous devriez toujours préférer les allocations de pile, dans la mesure où, en règle générale, vous ne devriez jamais avoir de nouveau/supprimer dans votre code utilisateur.

Comme vous le dites, lorsque la variable est déclarée sur la pile, son destructeur est automatiquement appelé lorsqu'il est hors de portée, ce qui est votre principal outil pour suivre la durée de vie des ressources et éviter les fuites.

Ainsi, en général, chaque fois que vous devez allouer une ressource, que ce soit de la mémoire (en appelant new), des descripteurs de fichier, des sockets ou toute autre chose, enveloppez-la dans une classe où le constructeur acquiert la ressource et que le destructeur la libère. Ensuite, vous pouvez créer un objet de ce type sur la pile et vous avez la garantie que votre ressource sera libérée quand elle sortira du domaine. Ainsi, vous n'avez pas à suivre vos paires nouvelles/supprimées partout pour éviter les fuites de mémoire.

Le nom le plus courant pour cet idiome est RAII

Examinez également les classes de pointeurs intelligents utilisées pour encapsuler les pointeurs résultants dans les rares cas où vous devez allouer quelque chose avec du nouveau en dehors d'un objet RAII dédié. Au lieu de cela, vous transmettez le pointeur à un pointeur intelligent, qui en suit ensuite la durée, par exemple en comptant le nombre de références, et appelle le destructeur lorsque la dernière référence sort de la portée. La bibliothèque standard a std::unique_ptr pour une gestion simple basée sur la portée, et std::shared_ptr qui fait le comptage de références pour mettre en œuvre la propriété partagée.

De nombreux tutoriels illustrent l'instanciation d'objets à l'aide d'un extrait tel que ...

Donc, ce que vous avez découvert, c’est que la plupart des tutoriels sont nuls. ;) La plupart des didacticiels vous apprennent de mauvaises pratiques C++, notamment appeler des nouvelles fonctions/supprimer pour créer des variables lorsque cela n’est pas nécessaire, et vous obliger à suivre de près la durée de vie de vos allocations.

162
jalf

Bien que disposer d'éléments sur la pile puisse constituer un avantage en termes d'allocation et de libération automatique, cela présente certains inconvénients.

  1. Vous pourriez ne pas vouloir allouer des objets énormes sur la pile.

  2. Envoi dynamique! Considérons ce code:

#include <iostream>

class A {
public:
  virtual void f();
  virtual ~A() {}
};

class B : public A {
public:
  virtual void f();
};

void A::f() {cout << "A";}
void B::f() {cout << "B";}

int main(void) {
  A *a = new B();
  a->f();
  delete a;
  return 0;
}

Cela affichera "B". Voyons maintenant ce qui se passe lors de l’utilisation de Stack:

int main(void) {
  A a = B();
  a.f();
  return 0;
}

Cela affichera "A", ce qui peut ne pas être intuitif pour ceux qui sont familiers avec Java ou d'autres langages orientés objet. La raison en est que vous n'avez pas de pointeur sur une instance de B plus, à la place, une instance de B est créée et copiée dans la variable a de type A.

Certaines choses peuvent se produire de manière non intuitive, notamment lorsque vous débutez en C++. En C, vous avez vos pointeurs et c'est tout. Vous savez comment les utiliser et ils font TOUJOURS la même chose. En C++, ce n'est pas le cas. Imaginez ce qui se passe lorsque vous utilisez un dans cet exemple comme argument pour une méthode - les choses se compliquent et cela FAIT une énorme différence si a est de type A ou A* ou même A& (appel par référence). De nombreuses combinaisons sont possibles et se comportent toutes différemment.

21
UniversE

Eh bien, la raison d'utiliser le pointeur serait exactement la même que celle d'utiliser des pointeurs en C alloués avec malloc: si vous voulez que votre objet vive plus longtemps que votre variable!

Il est même fortement recommandé de NE PAS utiliser le nouvel opérateur si vous pouvez l'éviter. Surtout si vous utilisez des exceptions. En général, il est beaucoup plus prudent de laisser le compilateur libérer vos objets.

13
PierreBdR

J'ai vu cet anti-modèle de la part de personnes qui ne comprennent pas exactement l'opérateur & l'adresse-de. S'ils ont besoin d'appeler une fonction avec un pointeur, ils alloueront toujours sur le tas pour obtenir un pointeur.

void FeedTheDog(Dog* hungryDog);

Dog* badDog = new Dog;
FeedTheDog(badDog);
delete badDog;

Dog goodDog;
FeedTheDog(&goodDog);
13
Mark Ransom

Traitez tas comme un bien immobilier très important et utilisez-le très judicieusement. La règle de base consiste à utiliser stack autant que possible et à utiliser tas chaque fois qu'il n'y a pas d'autre moyen. En allouant les objets sur la pile, vous pouvez obtenir de nombreux avantages tels que:

(1). Vous n'avez pas à vous soucier du déroulement de la pile en cas d'exception

(2). Ne vous inquiétez pas de la fragmentation de la mémoire causée par l'allocation de plus d'espace que nécessaire par votre gestionnaire de segments de mémoire.

7
Naveen

La seule raison pour laquelle je m'inquiète, c'est que Dog est maintenant alloué sur la pile, plutôt que sur le tas. Donc, si Dog a une taille en mégaoctets, vous pouvez avoir un problème,

Si vous devez utiliser le nouvel itinéraire/supprimer l'itinéraire, méfiez-vous des exceptions. Et pour cette raison, vous devez utiliser auto_ptr ou l’un des types de pointeurs intelligents boost pour gérer la durée de vie des objets.

5
Roddy

Il n'y a aucune raison de nouveau (sur le tas) lorsque vous pouvez allouer sur la pile (sauf si, pour une raison quelconque, vous avez une petite pile et souhaitez utiliser le tas.

Vous pouvez envisager d'utiliser un shared_ptr (ou l'une de ses variantes) de la bibliothèque standard si vous souhaitez allouer sur le tas. Cela gérera la suppression pour vous une fois que toutes les références au shared_ptr seront épuisées.

1
Scott Langham

Il y a une raison supplémentaire, que personne d'autre n'a mentionnée, pour laquelle vous pourriez choisir de créer votre objet de manière dynamique. Les objets dynamiques basés sur le tas vous permettent d'utiliser polymorphisme .

0
dangerousdave