Allocation dynamic d’un tableau d’objects

C’est une sorte de question pour les débutants, mais je n’ai pas fait de C ++ depuis longtemps, donc voilà …

J’ai une classe qui contient un tableau alloué dynamicment, disons

class A { int* myArray; A() { myArray = 0; } A(int size) { myArray = new int[size]; } ~A() { // Note that as per MikeB's helpful style critique, no need to check against 0. delete [] myArray; } } 

Mais maintenant, je veux créer un tableau de ces classes alloué dynamicment. Voici mon code actuel:

 A* arrayOfAs = new A[5]; for (int i = 0; i < 5; ++i) { arrayOfAs[i] = A(3); } 

Mais ça explose terriblement. Parce que le nouvel object A créé (avec l’appel A(3) ) est détruit à la fin for itération for loop, et cela signifie que le myArray interne de cette instance A est delete [] -ed.

Donc je pense que ma syntaxe doit être terriblement mauvaise? Je suppose qu’il y a quelques corrections qui semblent excessives, ce que j’espère éviter:

Je pense que c’est juste une chose de débutant où il y a une syntaxe qui fonctionne réellement en essayant d’allouer dynamicment un tableau de choses qui ont l’allocation dynamic interne.

(En outre, les critiques de style ont été appréciées, depuis que je fais du C ++ depuis un moment.)

Mise à jour pour les futurs téléspectateurs : toutes les réponses ci-dessous sont vraiment utiles. Martin est accepté à cause de l’exemple de code et de la “règle de 4”, mais je suggère vraiment de tous les lire. Certains sont de bons énoncés succincts de ce qui ne va pas, et certains soulignent correctement comment et pourquoi les vector sont une bonne solution.

Pour construire des conteneurs, vous devez évidemment utiliser l’un des conteneurs standard (tel qu’un std :: vector). Mais ceci est un exemple parfait des choses que vous devez considérer lorsque votre object contient des pointeurs RAW.

Si votre object a un pointeur RAW, vous devez vous souvenir de la règle 3 (maintenant la règle de 5 dans C ++ 11).

  • Constructeur
  • Destructeur
  • Constructeur de copie
  • Opérateur d’assignation
  • Déplacer le constructeur (C ++ 11)
  • Déplacer une affectation (C ++ 11)

En effet, si ce n’est pas défini, le compilateur générera sa propre version de ces méthodes (voir ci-dessous). Les versions générées par le compilateur ne sont pas toujours utiles lorsque vous utilisez des pointeurs RAW.

Le constructeur de copie est le plus difficile à corriger (il n’est pas sortingvial si vous voulez fournir la garantie forte d’exception). L’opérateur d’affectation peut être défini en termes de constructeur de copie car vous pouvez utiliser l’idiome de copie et d’échange en interne.

Voir ci-dessous pour plus de détails sur le minimum absolu pour une classe contenant un pointeur sur un tableau d’entiers.

Sachant qu’il n’est pas sortingvial de faire en sorte que ce soit correct, vous devriez envisager d’utiliser std :: vector plutôt qu’un pointeur sur un tableau d’entiers. Le vecteur est facile à utiliser (et à développer) et couvre tous les problèmes associés aux exceptions. Comparez la classe suivante avec la définition de A ci-dessous.

 class A { std::vector mArray; public: A(){} A(size_t s) :mArray(s) {} }; 

En regardant votre problème:

 A* arrayOfAs = new A[5]; for (int i = 0; i < 5; ++i) { // As you surmised the problem is on this line. arrayOfAs[i] = A(3); // What is happening: // 1) A(3) Build your A object (fine) // 2) A::operator=(A const&) is called to assign the value // onto the result of the array access. Because you did // not define this operator the compiler generated one is // used. } 

L'opérateur d'affectation généré par le compilateur convient à presque toutes les situations, mais lorsque vous utilisez des pointeurs RAW, vous devez faire attention. Dans votre cas, cela pose un problème à cause du problème de copie superficielle . Vous avez fini avec deux objects qui contiennent des pointeurs vers le même morceau de mémoire. Lorsque le A (3) sort de la scope à la fin de la boucle, il appelle delete [] sur son pointeur. Ainsi, l'autre object (dans le tableau) contient maintenant un pointeur sur la mémoire qui a été retourné au système.

Le constructeur de copie généré par le compilateur ; copie chaque variable membre en utilisant le constructeur de copie membre. Pour les pointeurs, cela signifie simplement que la valeur du pointeur est copiée de l'object source vers l'object de destination (d'où une copie superficielle).

L'opérateur d'affectation généré par le compilateur ; copie chaque variable membre en utilisant cet opérateur d'affectation de membres. Pour les pointeurs, cela signifie simplement que la valeur du pointeur est copiée de l'object source vers l'object de destination (d'où une copie superficielle).

Donc, le minimum pour une classe qui contient un pointeur:

 class A { size_t mSize; int* mArray; public: // Simple constructor/destructor are obvious. A(size_t s = 0) {mSize=s;mArray = new int[mSize];} ~A() {delete [] mArray;} // Copy constructor needs more work A(A const& copy) { mSize = copy.mSize; mArray = new int[copy.mSize]; // Don't need to worry about copying integers. // But if the object has a copy constructor then // it would also need to worry about throws from the copy constructor. std::copy(&copy.mArray[0],&copy.mArray[c.mSize],mArray); } // Define assignment operator in terms of the copy constructor // Modified: There is a slight twist to the copy swap idiom, that you can // Remove the manual copy made by passing the rhs by value thus // providing an implicit copy generated by the comstackr. A& operator=(A rhs) // Pass by value (thus generating a copy) { rhs.swap(*this); // Now swap data with the copy. // The rhs parameter will delete the array when it // goes out of scope at the end of the function return *this; } void swap(A& s) noexcept { using std::swap; swap(this.mArray,s.mArray); swap(this.mSize ,s.mSize); } // C++11 A(A&& src) noexcept : mSize(0) , mArray(NULL) { src.swap(*this); } A& operator=(A&& src) noexcept { src.swap(*this); // You are moving the state of the src object // into this one. The state of the src object // after the move must be valid but indeterminate. // // The easiest way to do this is to swap the states // of the two objects. // // Note: Doing any operation on src after a move // is risky (apart from destroy) until you put it // into a specific state. Your object should have // appropriate methods for this. // // Example: Assignment (operator = should work). // std::vector() has clear() which sets // a specific state without needing to // know the current state. return *this; } } 

Je recommande d’utiliser std :: vector: quelque chose comme

 typedef std::vector A; typedef std::vector AS; 

Il n’ya rien de mal à maîsortingser la STL et vous pourrez consacrer plus de temps à l’implémentation des fonctionnalités spécifiques de votre application au lieu de réinventer le vélo.

Le constructeur de votre object A alloue dynamicment un autre object et stocke un pointeur sur cet object alloué dynamicment dans un pointeur brut.

Pour ce scénario, vous devez définir votre propre constructeur de copie, votre opérateur d’affectation et votre destructeur. Les compilateurs générés ne fonctionneront pas correctement. (Ceci est un corollaire de la «loi des trois grands»: une classe avec n’importe quel destructeur, opérateur d’assignation, constructeur de copie, a généralement besoin de tous les 3).

Vous avez défini votre propre destructeur (et vous avez mentionné la création d’un constructeur de copie), mais vous devez définir les deux autres des trois grands.

Une alternative consiste à stocker le pointeur sur votre int[] dynamicment alloué dans un autre object qui s’occupera de ces choses pour vous. Quelque chose comme un vector (comme vous l’avez mentionné) ou un boost::shared_array<> .

Pour résumer cela – pour tirer pleinement parti de RAII, évitez autant que possible de manipuler des pointeurs bruts.

Et comme vous avez demandé d’autres critiques de style, une autre est que lorsque vous supprimez des pointeurs bruts, vous n’avez pas besoin de vérifier 0 avant d’appeler delete . les chèques.

  1. Utilisez le tableau ou le conteneur commun pour les objects uniquement s’ils ont des constructeurs par défaut et des copies.

  2. Stockez des pointeurs autrement (ou des pointeurs intelligents, mais peuvent rencontrer certains problèmes dans ce cas).

PS: définissez toujours les parameters par défaut et copiez les constructeurs qui seront générés automatiquement

Vous avez besoin d’un opérateur d’affectation pour que:

 arrayOfAs[i] = A(3); 

fonctionne comme il se doit.

Pourquoi ne pas avoir une méthode setSize.

 A* arrayOfAs = new A[5]; for (int i = 0; i < 5; ++i) { arrayOfAs[i].SetSize(3); } 

J'aime la "copie" mais dans ce cas le constructeur par défaut ne fait rien. Le SetSize pourrait copier les données en dehors du m_array original (s'il existe). Vous devez stocker la taille du tableau dans la classe pour ce faire.
OU
Le SetSize pourrait supprimer le m_array original.

 void SetSize(unsigned int p_newSize) { //I don't care if it's null because delete is smart enough to deal with that. delete myArray; myArray = new int[p_newSize]; ASSERT(myArray); } 

En utilisant la fonctionnalité de placement du new opérateur, vous pouvez créer l’object en place et éviter de copier:

placement (3): void * operator new (std :: size_t size, void * ptr) noexcept;

Retourne simplement ptr (aucun stockage n’est alloué). Notez cependant que si la fonction est appelée par une nouvelle expression, l’initialisation correcte sera effectuée (pour les objects de classe, cela inclut l’appel de son constructeur par défaut).

Je suggère ce qui suit:

 A* arrayOfAs = new A[5]; //Allocate a block of memory for 5 objects for (int i = 0; i < 5; ++i) { //Do not allocate memory, //initialize an object in memory address provided by the pointer new (&arrayOfAs[i]) A(3); }