37 votes

malloc & placement nouveau vs nouveau

J'ai été à la recherche dans ce depuis quelques jours, et jusqu'à présent je n'ai pas vraiment trouvé de quoi convaincre les autres que dogmatiques des arguments ou des appels à la tradition (c'est à dire "c'est le C++ façon!").

Si je crée un tableau d'objets, ce est la raison impérieuse (autres que la facilité) pour l'utilisation:

#define MY_ARRAY_SIZE 10

//  ...

my_object * my_array=new my_object [MY_ARRAY_SIZE];

for (int i=0;i<MY_ARRAY_SIZE;++i) my_array[i]=my_object(i);

plus

#define MEMORY_ERROR -1
#define MY_ARRAY_SIZE 10

//  ...

my_object * my_array=(my_object *)malloc(sizeof(my_object)*MY_ARRAY_SIZE);
if (my_object==NULL) throw MEMORY_ERROR;

for (int i=0;i<MY_ARRAY_SIZE;++i) new (my_array+i) my_object (i);

Aussi loin que je peux dire que ce dernier est beaucoup plus efficace que l'ancien (puisque vous n'avez pas initialiser la mémoire de certains non-valeur aléatoire/appel constructeurs par défaut inutilement), et la seule différence est vraiment le fait que l'un de vous nettoyer avec:

delete [] my_array;

et les autres vous nettoyer avec:

for (int i=0;i<MY_ARRAY_SIZE;++i) my_array[i].~T();

free(my_array);

Je suis pour une impérieuse raison. Appels sur le fait que c'est du C++ (C) et, par conséquent, malloc et free ne doit pas être utilisé ne l'est pas, autant que je puisse en juger - impérieux tant qu'il est dogmatique. Il y a une chose qui me manque, qui fait new [] supérieure à malloc?

Je veux dire, du mieux que je peux dire, vous ne pouvez même pas utiliser new [] -- -- en faire une variété de choses qui n'ont pas de défaut, constructeur sans paramètre, alors que l' malloc méthode peut donc être utilisée.

63voto

Nicol Bolas Points 133791

Je suis pour une raison convaincante.

Cela dépend de comment vous définissez "convaincant". De nombreux arguments vous ont jusqu'à présent rejeté sont certainement irrésistible pour la plupart des programmeurs C++, que votre suggestion n'est pas la norme de façon à allouer à nu les tableaux en C++.

Le simple fait est-ce: oui, vous ne pouvez absolument faire les choses de la façon dont vous décrivez. Il n'y a pas de raison que ce que vous décrivez ne sera pas fonction.

Mais là encore, vous pouvez avoir des fonctions virtuelles en c. Vous pouvez mettre en œuvre des classes et héritage dans la plaine de C, si vous mettez le temps et d'efforts. Ceux-ci sont entièrement fonctionnelles.

Par conséquent, ce qui importe n'est pas de savoir si quelque chose peut travailler. Mais plus sur ce que les frais sont. C'est beaucoup plus sujettes à erreur de mettre en œuvre l'héritage et les fonctions virtuelles en C qu'en C++. Il existe plusieurs façons de mettre en œuvre en C, ce qui conduit à incompatibles implémentations. Attendu que, parce qu'ils sont de première classe de langue fonctionnalités de C++, il est très peu probable que quelqu'un aurait manuellement mettre en œuvre ce que la langue offre. Ainsi, tout le monde à l'héritage et les fonctions virtuelles peuvent coopérer avec les règles de C++.

Il en va de même pour ce. Alors, quels sont les gains et les pertes de manuel malloc/free de gestion de la matrice?

Je ne peux pas dire que tout ce que je suis sur le point de dire constitue une "raison impérieuse" pour vous. J'en doute, puisque vous semblez avoir fait de votre esprit. Mais pour l'enregistrement:

Performance

Vous demander ce qui suit:

Aussi loin que je peux dire que ce dernier est beaucoup plus efficace que l'ancien (puisque vous n'avez pas initialiser la mémoire de certains non-valeur aléatoire/appel constructeurs par défaut inutilement), et la seule différence est vraiment le fait que l'un de vous nettoyer avec:

Cette déclaration suggère que le gain d'efficacité est principalement dans la construction des objets en question. Qui est qui, les constructeurs sont appelés. La déclaration suppose que vous n'avez pas envie d'appeler le constructeur par défaut; l'utilisation d'un constructeur par défaut juste pour créer le tableau, puis la fonction d'initialisation de mettre les données réelles de l'objet.

Eh bien... si ce n'est pas ce que vous voulez faire? Si ce que vous voulez faire est de créer un vide tableau, celui qui est par défaut construit? Dans ce cas, cet avantage disparaît entièrement.

La fragilité

Supposons que chaque objet du tableau doit avoir un constructeur spécialisé ou quelque chose appelé sur elle, tels que l'initialisation de la matrice nécessite ce genre de chose. Mais considérez votre code de destruction:

for (int i=0;i<MY_ARRAY_SIZE;++i) my_array[i].~T();

Pour un cas simple, c'est très bien. Vous disposez d'une macro ou const variable qui indique le nombre d'objets que vous avez. Et vous passez en boucle sur chaque élément de détruire les données. C'est idéal pour un exemple simple.

Considérons maintenant une vraie application, pas un exemple. Comment de nombreux endroits différents, serez-vous de la création d'un tableau? Des dizaines d'autres? Des centaines? Chacun devra avoir sa propre for boucle pour l'initialisation de la matrice. Chacun devra avoir sa propre for boucle pour la destruction de la matrice.

Sig de type ce, même une fois, et vous pouvez corrompre la mémoire. Ou de ne pas supprimer quelque chose. Ou n'importe quel nombre d'autres choses horribles.

Et voici une question importante: pour un tableau donné, où gardez-vous de la taille? Savez-vous combien d'articles que vous avez alloué pour chaque tableau que vous créez? Chaque tableau sera probablement avoir son propre moyen de savoir combien d'éléments il stocke. De sorte que chaque destructeur boucle besoin de récupérer ces données correctement. Si c'est faux... boom.

Et puis nous avons exception de la sécurité, qui est une toute nouvelle boîte de pandore. Si l'un des constructeurs déclenche une exception, construits précédemment objets doivent être détruits. Votre code ne le fait pas; ce n'est pas une exception-safe.

Maintenant, considérez l'alternative:

delete[] my_array;

Cela ne peut pas échouer. Il sera toujours de détruire chaque élément. Elle s'ajuste à la taille du tableau, et c'est garanti sans exception. Il est garanti pour fonctionner. Il ne peut pas ne pas travailler (aussi longtemps que vous avez alloué, il avec new[]).

Bien sûr, vous pourriez dire que vous pouvez envelopper le tableau dans un objet. Qui fait sens. Vous pourriez même modèle de l'objet sur le type des éléments du tableau. De cette façon, tous les desturctor code est le même. La taille est contenue dans l'objet. Et peut-être, juste peut-être, vous vous rendez compte que l'utilisateur doit avoir un certain contrôle sur la façon particulière de la mémoire est allouée, alors que ce n'est pas seulement malloc/free.

Félicitations: vous venez de ré-inventé std::vector.

C'est pourquoi de nombreux programmeurs C++ ne sont pas de même type new[] plus.

La flexibilité

Votre code utilise malloc/free. Mais disons que je suis en train de faire certains profils. Et je me rends compte qu' malloc/free pour certains souvent créé des types est tout simplement trop cher. J'ai créer un gestionnaire de mémoire pour eux. Mais comment raccorder l'ensemble de la matrice de crédits?

Eh bien, je recherche le code de base pour n'importe quel endroit où vous créer/détruire des tableaux de ces types. Et puis, je dois changer leur allocateurs de mémoire en conséquence. Et puis, je dois continuellement regarder le code de base de sorte que quelqu'un d'autre ne change pas ces allocateurs de retour ou d'introduire une nouvelle matrice de code qui utilise différents allocateurs.

Si j'étais plutôt à l'aide de new[]/delete[], je pourrais utiliser la surcharge d'opérateur. J'ai simplement fournir une surcharge pour les opérateurs new[] et delete[] pour ces types. Pas de code a modifier. Il est beaucoup plus difficile pour quelqu'un de contourner ces surcharges; ils ont activement essayer. Et ainsi de suite.

Si je reçois une plus grande flexibilité et une assurance raisonnable que mon allocateurs sera utilisé là où ils devraient être utilisés.

La lisibilité

Réfléchissez à ceci:

my_object *my_array = new my_object[10];
for (int i=0; i<MY_ARRAY_SIZE; ++i)
  my_array[i]=my_object(i);

//... Do stuff with the array

delete [] my_array;

Le comparer à ceci:

my_object *my_array = (my_object *)malloc(sizeof(my_object) * MY_ARRAY_SIZE);
if(my_object==NULL)
  throw MEMORY_ERROR;

int i;
try
{
    for(i=0; i<MY_ARRAY_SIZE; ++i)
      new(my_array+i) my_object(i);
}
catch(...)  //Exception safety.
{
    for(i; i>0; --i)  //The i-th object was not successfully constructed
        my_array[i-1].~T();
    throw;
}

//... Do stuff with the array

for(int i=MY_ARRAY_SIZE; i>=0; --i)
  my_array[i].~T();
free(my_array);

Objectivement parlant, lequel des deux est plus facile à lire et à comprendre ce qu'il se passe?

Il suffit de regarder cette déclaration: (my_object *)malloc(sizeof(my_object) * MY_ARRAY_SIZE). C'est un très faible niveau de la chose. Vous n'êtes pas de l'allocation d'un tableau de rien; vous êtes d'allouer un bloc de mémoire. Vous avez à calculer manuellement la taille du morceau de la mémoire en fonction de la taille de l'objet * le nombre d'objets que vous le souhaitez. Il est même doté d'un casting.

En revanche, new my_object[10] raconte une histoire. new est le C++, le mot clé "créer des instances de types". my_object[10] est un tableau de 10 éléments d' my_object type. C'est simple, évidente et intuitive. Il n'y a pas de casting, pas de calcul de longueurs d'octets, rien.

L' malloc méthode nécessite l'apprentissage comment utiliser malloc idiomatique. L' new méthode nécessite juste de comprendre comment new travaux. C'est beaucoup moins verbeux et beaucoup plus évident que ce qui se passe.

En outre, après l' malloc déclaration, vous n'avez pas en fait d'un tableau d'objets. malloc retourne simplement un bloc de mémoire que vous avez dit au compilateur C++ pour faire semblant est un pointeur vers un objet (avec un plâtre). Il n'est pas un tableau d'objets, car les objets en C++ ont une durée de vie. Et la vie de l'objet ne commence pas jusqu'à ce qu'il est construit. Rien dans ce mémoire a eu un constructeur appelé sur elle encore, et, par conséquent, il n'y a pas de vie des objets à l'intérieur.

my_array à ce stade n'est pas un tableau, c'est juste un bloc de la mémoire. Il ne deviendra pas un tableau d' my_objects jusqu'à ce que vous construisez dans l'étape suivante. Ce est incroyablement pas intuitif pour un nouveau programmeur; il prend un expérimenté C++ à la main (quelqu'un qui a probablement appris à partir de C) à savoir que ce ne sont pas des objets vivants et doit être traité avec soin. Le pointeur de ne pas encore se comporter comme une véritable my_object*, car il n'a pas de point à tout my_objects encore.

En revanche, vous n' avez de vie des objets dans l' new[] des cas. Les objets ont été construits; ils sont vivants, et entièrement formée. Vous pouvez utiliser ce pointeur comme tous les autres my_object*.

Fin

Aucun des ci-dessus, dit que ce mécanisme n'est pas susceptible d'être utile dans certaines circonstances. Mais c'est une chose de reconnaître l'utilité de quelque chose dans certaines circonstances. C'en est une autre de dire qu'il devrait être le défaut façon de faire les choses.

37voto

Alok Save Points 115848

Si vous ne voulez pas d'obtenir votre mémoire initialisé par le constructeur implicite des appels, et juste besoin d'un assuré allocation de mémoire pour l' placement new alors qu'il est parfaitement acceptable d'utiliser malloc et free au lieu de new[] et delete[].

Le impérieuses raisons de l'utilisation de new sur malloc que new offre implicite de l'initialisation par constructeur appelle, elle vous épargne des memset ou liées à des appels de fonction de poster un malloc Et que, pour new vous n'avez pas besoin de vérifier pour NULL après chaque allocation, tout en joignant les gestionnaires d'exception va faire le travail, vous sauvant de la redondante de vérification d'erreur contrairement à malloc.
Ces deux raisons impérieuses ne s'appliquent pas à votre utilisation.

celui qui est de la performance efficace ne peut être déterminé que par le profilage, il n'y a rien de mal dans l'approche que vous avez maintenant. Sur une note de côté, je ne vois pas de raison convaincante pour expliquer pourquoi utiliser malloc sur new[] soit.

19voto

Loki Astari Points 116129

Je dirais ni l'un ni l'autre.

La meilleure façon de le faire serait:

 std::vector<my_object>   my_array;
my_array.reserve(MY_ARRAY_SIZE);

for (int i=0;i<MY_ARRAY_SIZE;++i)
{    my_array.push_back(my_object(i));
}
 

En effet, en interne, vector est probablement en train de faire le nouvel emplacement pour vous. Il gère également tous les autres problèmes associés à la gestion de la mémoire que vous ne prenez pas en compte.

10voto

justin Points 72871

Vous avez réimplémentée new[]/delete[] d'ici, et ce que vous avez écrit est assez commun dans le développement spécialisé allocateurs.

La surcharge de l'appel de constructeurs simples prennent peu de temps par rapport à la répartition. Ce n'est pas forcément beaucoup plus efficace", tout dépend de la complexité du constructeur par défaut, et d' operator=.

Une bonne chose qui n'a pas encore été mentionnés, c'est que la taille du tableau est connu par new[]/delete[]. delete[] a tout simplement le droit et détruit tous les éléments lorsque demandé. Glisser une variable supplémentaire (ou trois) autour de sorte que vous exactement comment détruire la matrice est une douleur. Une collection dédiée type serait une alternative idéale, cependant.

new[]/delete[] sont préférables pour des raisons de commodité. Ils introduisent un peu de surcharge, et pourrait vous sauver de beaucoup de stupides erreurs. Etes-vous obligé assez pour emporter cette fonctionnalité et l'utilisation d'une collection/conteneur partout à l'appui de votre construction sur mesure? J'ai mis en place cet allocateur de-la véritable gâchis, c'est la création de foncteurs pour toutes les variantes de fabrication vous besoin dans la pratique. En tout cas, vous avez souvent une plus précise de l'exécution au détriment d'un programme qui est souvent plus difficile à maintenir que les idiomes tout le monde le sait.

6voto

David C. Bishop Points 2107

À mon humble avis il y a à la fois laid, il est préférable d'utiliser des vecteurs. Assurez-vous d'allouer de l'espace à l'avance pour la performance.

Soit:

std::vector<my_object> my_array(MY_ARRAY_SIZE);

Si vous souhaitez initialiser avec une valeur par défaut pour toutes les entrées.

my_object basic;
std::vector<my_object> my_array(MY_ARRAY_SIZE, basic);

Ou si vous ne voulez pas de construire les objets, mais ne souhaitez réserver de l'espace:

std::vector<my_object> my_array;
my_array.reserve(MY_ARRAY_SIZE);

Alors si vous avez besoin d'y accéder dans un Style C pointeur de tableau (assurez-vous de ne pas ajouter des trucs tout en gardant l'ancien pointeur mais vous ne pouvez pas le faire avec la régulière de style c des tableaux de toute façon.)

my_object* carray = &my_array[0];      
my_object* carray = &my_array.front(); // Or the C++ way

Accéder aux éléments individuels:

my_object value = my_array[i];    // The non-safe c-like faster way
my_object value = my_array.at(i); // With bounds checking, throws range exception

Typedef pour la jolie:

typedef std::vector<my_object> object_vect;

Les passer autour de fonctions avec les références:

void some_function(const object_vect& my_array);

EDIT: EN C++11, il est également std::array. Le problème c'est bien que c'est la taille se fait par l'intermédiaire d'un modèle de sorte que vous ne pouvez pas faire de différentes entreprises au moment de l'exécution et tu ne peux pas le passer en fonctions, à moins qu'ils attendent exactement la même taille (ou modèle de fonctions elles-mêmes). Mais il peut être utile pour des choses comme des tampons.

std::array<int, 1024> my_array;

EDIT2: Aussi en C++11, il est une nouvelle emplace_back comme une alternative à push_back. Cela permet essentiellement à "évoluer" votre objet (ou construire votre objet directement dans le vecteur) et vous permet d'économiser une copie.

std::vector<SomeClass> v;
SomeClass bob {"Bob", "Ross", 10.34f};
v.emplace_back(bob);
v.emplace_back("Another", "One", 111.0f); // <- Note this doesn't work with initialization lists ☹

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