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_object
s 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_object
s 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.