959 votes

Pourquoi les programmeurs C++ devraient minimiser l’utilisation de ' nouveau ' ?

Je suis tombé sur une fuite de Mémoire avec std::string lors de l'utilisation de std::list<std::string>, et l'un des commentaires dit ceci:

Cesser d'utiliser new tellement. Je ne vois aucune raison pour que vous utilisé n'importe où vous l'avez fait. Vous pouvez créer des objets par valeur en C++ et c'est l'un des d'énormes avantages à l'utilisation de la langue. Vous n'avez pas à allouer tout sur la pile. Arrêter de penser comme un programmeur Java.

Je ne suis pas vraiment sûr de ce qu'il veut dire par là. Quelqu'un pourrait-il expliquer pourquoi les objets doivent être créés par valeur en C++ aussi souvent que possible, et sur la différence qu'il fait à l'interne? Si j'ai mal interprété la réponse, n'hésitez pas à préciser ce qu'on entend.

1140voto

André Caron Points 19543

Il y a deux largement utilisés des techniques d'allocation de mémoire: automatique allocation et l'allocation dynamique. Généralement, il est une région correspondante de la mémoire pour chacun: la pile et le tas.

Pile

La pile toujours alloue de la mémoire, de façon séquentielle. Il peut le faire parce qu'il vous oblige à libérer de la mémoire dans l'ordre inverse (Premier entré, Dernier Sorti: FILO). C'est l'allocation de mémoire technique pour les variables locales dans de nombreux langages de programmation. Il est très, très rapide, car il nécessite un minimum de tenue de livres et la prochaine adresse à allouer est implicite.

En C++, cela s'appelle du stockage automatique , car le stockage est revendiquée automatiquement à la fin de la portée. Dès que l'exécution de l'actuel bloc de code (délimité à l'aide de {}) est terminée, la mémoire pour toutes les variables de ce bloc est automatiquement collectées. C'est aussi le moment où les destructeurs sont appelés à nettoyer les ressources.

Tas

Le tas permet une plus souple de l'allocation de mémoire de mode. La comptabilité est plus complexe et la répartition est plus lent. Car il n'est pas implicite point de lâcher, vous devez libérer la mémoire manuellement, à l'aide de delete ou delete[] (free en C). Cependant, l'absence d'un accord implicite de la libération point est la clé pour le segment de la flexibilité.

Raisons de l'utilisation de l'allocation dynamique

Même si le tas est plus lente et peut entraîner des fuites de mémoire ou de fragmentation de mémoire, il y a parfaitement bien des cas d'utilisation pour l'allocation dynamique, car elle est moins limitée.

Deux raisons principales pour l'utilisation de l'allocation dynamique:

  • Vous ne savez pas combien de mémoire vous avez besoin au moment de la compilation. Par exemple, lors de la lecture d'un fichier texte dans une chaîne de caractères, généralement, vous ne savez pas quelle est la taille du fichier, de sorte que vous ne pouvez pas décider de la quantité de mémoire à allouer jusqu'à ce que vous exécutez le programme.

  • Vous souhaitez allouer de la mémoire qui vont persister après la sortie du bloc courant. Par exemple, vous pouvez écrire une fonction string readfile(string path) qui renvoie le contenu d'un fichier. Dans ce cas, même si la pile pourrait tenir l'ensemble du contenu du fichier, vous ne pouviez pas de retour de la fonction et de garder le bloc de mémoire allouée.

Pourquoi l'allocation dynamique est souvent inutile

En C++ il y a une jolie construction appelée un destructeur. Ce mécanisme permet de gérer les ressources en alignant la durée de vie de la ressource avec la durée de vie d'une variable. Il est couramment utilisé pour "envelopper" les ressources dans un autre objet. std::string est un parfait exemple. Cet extrait:

int main ( int argc, char* argv[] )
{
    std::string program(argv[0]);
}

en fait alloue un montant variable de la mémoire. L' std::string objet alloue de la mémoire à l'aide de la tas et la libère dans son destructeur. Dans ce cas, vous n'avez pas besoin de gérer manuellement toutes les ressources et encore obtenir les avantages de l'allocation dynamique de la mémoire.

En particulier, il implique que, dans cet extrait:

int main ( int argc, char* argv[] )
{
    std::string * program = new std::string(argv[0]);
    delete program;
}

il est inutile de l'allocation dynamique de la mémoire. Le programme nécessite plus de frappe (!) et introduit le risque d'oublier de libérer la mémoire. Il le fait avec aucun avantage apparent.

Pourquoi devriez-vous utiliser le stockage automatique aussi souvent que possible

Fondamentalement, le dernier paragraphe résume. À l'aide de stockage automatique aussi souvent que possible vos programmes:

  • plus rapide de taper;
  • plus rapide lors de l'exécution;
  • moins sujettes à la mémoire de ressources de fuites.

Les points de Bonus

Référencé dans la question, il y a d'autres préoccupations. En particulier, la classe suivante:

class Line {
public:
    Line();
    ~Line();
    std::string* mString;
};

Line::Line() {
    mString = new std::string("foo_bar");
}

Line::~Line() {
   //mString->clear(); // should not be neccessary
    delete mString;
}

Est en réalité beaucoup plus risqué d'utiliser que la suivante:

class Line {
public:
    Line();
    std::string mString;
};

Line::Line() {
    mString = "foo_bar";
    // note: there is a cleaner way to write this.
}

La raison en est qu' std::string correctement définit un constructeur de copie. Considérons le programme suivant:

int main ()
{
    Line l1;
    Line l2 = l1;
}

À l'aide de la version d'origine, ce programme sera probablement crash, comme il utilise delete sur la même chaîne à deux reprises. À l'aide de la version modifiée, chaque Line instance propre sa propre chaîne de l'instance, chacune avec sa propre mémoire, et les deux seront publiées à la fin du programme.

D'autres notes

L'utilisation intensive de RAII est considéré comme une meilleure pratique en C++ à cause de toutes les raisons ci-dessus. Cependant, il y a un avantage supplémentaire qui n'est pas immédiatement évidente. En gros, c'est mieux que la somme de ses parties. L'ensemble du mécanisme qui le compose. Il évolue.

Si vous utilisez l' Line classe comme un bloc de construction:

 class Table
 {
      Line borders[4];
 };

Alors

 int main ()
 {
     Table table;
 }

alloue quatre std::string des cas, quatre Line des cas, Table de l'instance et de toute la chaîne du matières et tout est libérée automatiquement.

182voto

DigitalRoss Points 80400
<h3>Parce que la pile est rapide et infaillible</h3> <p>En C++, il faut, mais une seule instruction à allouer de l’espace--sur la pile--pour chaque objet de portée locale dans une fonction donnée et il est impossible de fuir cette mémoire. Que commentaire destiné (ou devrait avoir l’intention) de dire quelque chose comme <em>« utiliser la pile et non sur le tas ».</em></p>

119voto

Nicol Bolas Points 133791

C'est compliqué.

Tout d'abord, le C++ n'est pas nettoyée. Par conséquent, pour chaque nouvelle, il doit y avoir une suppression correspondante. Si vous ne parvenez pas à mettre cette suppression, vous avez une fuite de mémoire. Maintenant, pour un cas simple comme ceci:

std::string *someString = new std::string(...);
//Do stuff
delete someString;

C'est simple. Mais qu'advient-il si "Faire des trucs" déclenche une exception? Oups: fuite de mémoire. Qu'advient-il si "Faire des trucs" questions d' return début? Oups: fuite de mémoire.

Et c'est le plus simple des cas. Si vous êtes de retour d'une chaîne à quelqu'un, maintenant ils ont à la supprimer. Et si ils passent, comme un argument, la personne reçoit-il besoin de le supprimer? Quand faut-il les supprimer?

Ou, vous pouvez simplement faire ceci:

std::string someString(...);
//Do stuff

Pas de delete. L'objet a été créé sur la "pile", et il sera détruit une fois qu'il est hors de portée. Vous pouvez même retourner l'objet, donc le transfert de son contenu à l'appel de la fonction. Vous pouvez passer de l'objet à des fonctions (généralement sous la forme d'un renvoi ou d'const-référence: void SomeFunc(std::string &iCanModifyThis, const std::string &iCantModifyThis). Et ainsi de suite.

Tous, sans new et delete. Il n'y a aucune question de savoir qui est propriétaire de la mémoire ou qui est responsable de la suppression. Si vous ne:

std::string someString(...);
std::string otherString;
otherString = someString;

Il est entendu que l' otherString a une copie des données de l' someString. Ce n'est pas un pointeur; c'est un objet séparé. Ils peuvent avoir le même contenu, mais vous pouvez modifier l'un sans affecter l'autre:

someString += "More text.";
if(otherString == someString) { /*Will never get here */ }

Voyez l'idée?

78voto

Seva Alekseyev Points 31812

Les objets créés par new doit être, éventuellement, deleted, de peur de la fuite. Le destructeur ne sera pas appelé, la mémoire n'est pas libérée, le bit de l'ensemble. Depuis C++ n'a pas de collecte des ordures, c'est un problème.

Les objets créés par la valeur (j'. e. sur la pile) automatiquement meurent quand ils passent hors de portée. Le destructeur d'appel est inséré par le compilateur, et la mémoire est automatiquement libérée à la fonction de retour.

Pointeurs intelligents comme auto_ptr, shared_ptr résoudre le balançant de référence de problème, mais ils ont besoin de codage de la discipline et avoir d'autres problèmes (copyability, les boucles de référence, etc.).

Aussi, fortement multithread scénarios, new est un point de discorde entre les threads; il peut y avoir un impact sur les performances de la surutilisation new. Pile de création de l'objet est par définition locale de thread, puisque chaque thread possède sa propre pile.

La baisse de la valeur des objets est qu'ils meurent une fois que l'hôte de la fonction renvoie - vous ne pouvez pas passer une référence pour ceux qui sont à l'appelant, que par la copie ou le retour en valeur.

33voto

sarat Points 2337
  • C++ n'est pas employer n'importe quel gestionnaire de mémoire par ses propres. D'autres langages comme C#, Java a garbage collector pour gérer la mémoire
  • C++ à l'aide de routines de système d'exploitation pour allouer de la mémoire et trop de new/delete pourrait fragment de la mémoire disponible
  • Avec n'importe quelle application, si la mémoire est fréquemment utilisé, il est conseillé de pré-allouer et libérer lorsqu'il n'est pas nécessaire.
  • Une mauvaise gestion de la mémoire pourrait entraîner des fuites de mémoire et c'est vraiment difficile à suivre. Donc à l'aide d'empiler des objets dans le champ d'application de la fonction est une technique éprouvée
  • L'inconvénient de l'utilisation de la pile des objets, il crée de multiples copies d'objets sur le retour, en passant à des fonctions, etc. Cependant les compilateurs intelligentes sont bien conscients de ces situations et ils ont été bien optimisé pour les performances
  • C'est vraiment pénible en C++ si la mémoire est allouée et publié dans deux endroits différents. La responsabilité de la libération est toujours une question et surtout, nous comptons sur certaines couramment accessibles pointeurs, objets de pile (maximum possible) et des techniques comme auto_ptr (RAII objets)
  • La meilleure chose est que, vous avez le contrôle sur la mémoire et le pire, c'est que vous n'avez aucun contrôle sur la mémoire si nous employons une mauvaise gestion de la mémoire de l'application. Les pannes dues à des corruptions de mémoire sont les pires dans le monde

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