60 votes

Allouer dynamiquement un tableau d'objets

C'est le genre de débutants question, mais je n'ai pas fait de C++ dans un temps long, alors voilà...

J'ai une classe qui contient un tableau alloué dynamiquement, dire

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 alloué dynamiquement de ces classes. Voici mon code actuel:

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

Mais ce souffle terriblement. Parce que le nouveau A objet créé (avec l' A(3) appel) est détruite lors de l' for itération de boucle se termine, et cela signifie qu'à l'interne, myArray de A instance bénéficie d' delete []-ed.

Je pense donc que ma syntaxe doit être terriblement mal? Je suppose qu'il ya quelques correctifs sembler excessif, qui, je l'espère à éviter:

  • La création d'un constructeur de copie pour A.
  • À l'aide de vector<int> et vector<A> donc je n'ai pas à vous soucier de tout cela.
  • Au lieu d'avoir arrayOfAs être un tableau d' A objets, ont-il un tableau d' A* des pointeurs.

Je pense que c'est juste quelques débutants chose où il y a une syntaxe qui fonctionne réellement lors de la tentative d'allouer dynamiquement un tableau de choses qui ont de la dynamique interne de l'allocation.

(Aussi, le style, les critiques apprécié, car il a été un moment depuis que j'ai fait en C++.)

Mise à jour pour les futurs spectateurs: Toutes les réponses ci-dessous sont vraiment utiles. Martin est acceptée à cause de l'exemple de code et de la "règle des 4", mais vraiment, je suggère la lecture de tous. Certains sont bons, de la concision des déclarations de ce qui est mauvais, et certains soulignent correctement comment et pourquoi vectors sont une bonne façon d'aller.

127voto

Loki Astari Points 116129

Pour la construction des récipients de toute évidence vous souhaitez utiliser l'un des conteneurs standard (tel qu'un std::vector). Mais c'est un exemple parfait de choses que vous devez considérer lors de votre objet contient des pointeurs.

Si votre objet est un pointeur BRUT, alors vous devez vous rappeler de la règle de 3 (maintenant la règle de 5 en C++11).

  • Constructeur
  • Destructeur
  • Constructeur De Copie
  • Opérateur D'Affectation
  • Constructeur De Déplacement (C++11)
  • Assignation De Déplacement (C++11)

C'est parce que si pas défini le compilateur va générer sa propre version de ces méthodes (voir ci-dessous). Le compilateur a généré versions ne sont pas toujours utiles lorsque vous traitez avec des pointeurs.

Le constructeur de copie est la plus difficile à obtenir correct (c'est non trivial si vous souhaitez fournir la forte exception de garantie). L'opérateur d'Affectation peut être définie en termes de Constructeur de Copie que vous pouvez utiliser le copier et le swap idiome à l'interne.

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

Sachant qu'il est non trivial de l'obtenir correcte, vous devriez envisager d'utiliser std::vector plutôt qu'un pointeur vers un tableau d'entiers. Le vecteur est facile à utiliser (et développer) et couvre tous les problèmes associés avec des exceptions. Comparer la classe suivante avec la définition d'Un ci-dessous.

class A
{ 
    std::vector<int>   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.
}

Le compilateur a généré opérateur d'affectation est parfait pour presque toutes les situations, mais lorsque les indicateurs sont dans le jeu, vous devez prêter attention. Dans votre cas, il est à l'origine d'un problème en raison de la copie superficielle problème. Vous avez terminé avec deux objets qui contiennent des pointeurs vers le même élément de la mémoire. Quand l'Un(3) est hors de portée à la fin de la boucle il appelle delete [] sur son pointeur. Donc l'autre objet (dans le tableau) contient un pointeur vers la mémoire qui a été renvoyé dans le système.

Le compilateur a généré constructeur de copie, de copies de chaque membre de la variable à l'aide que les membres du constructeur de copie. Pour les pointeurs cela signifie simplement que la valeur du pointeur est copié à partir de l'objet source à la destination de l'objet (d'où la copie superficielle).

Le compilateur a généré opérateur d'affectation; copies de chaque membre de la variable à l'aide que les membres de l'opérateur d'affectation. Pour les pointeurs cela signifie simplement que la valeur du pointeur est copié à partir de l'objet source à la destination de l'objet (d'où la 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 idium, that you can
         //           Remove the manual copy made by passing the rhs by value thus
         //           providing an implicit copy generated by the compiler.
         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) throws ()
         {
             std::swap(this.mArray,s.mArray);
             std::swap(this.mSize ,s.mSize);
         }

         // C++11
         A(A&& src) noexcept
             : mSize(0)
             , mArray(NULL)
         {
             (*this) = std::move(src); // Implements in terms of assignment
         }
         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
                                  // 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 shoudl 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.
         }   
 }

12voto

IMil Points 526

Je vous recommande d'utiliser std::vector: quelque chose comme

typedef std::vector<int> A;
typedef std::vector<A> AS;

Il n'y a rien de mal avec le léger excès de STL, et vous serez en mesure de passer plus de temps à la mise en œuvre des fonctionnalités spécifiques de votre application plutôt que de réinventer la bicyclette.

8voto

Michael Burr Points 181287

Le constructeur de votre Un objet alloue un autre objet de façon dynamique et stocke un pointeur vers cet objet alloué dynamiquement dans un pointeur brut.

Pour ce scénario, vous devez définir votre propre constructeur de copie , opérateur d'affectation et destructeur. Le compilateur a généré plus de ne pas fonctionner correctement. (Ce qui est un corollaire de la "Loi des Trois Grands": Une classe avec tout de destructeur, opérateur d'affectation, constructeur de copie doit généralement tous les 3).

Vous avez défini votre propre destructeur (et vous l'avez mentionné la création d'un constructeur de copie), mais vous devez définir à la fois de l'autre 2 des trois grands.

Une alternative consiste à stocker le pointeur de votre allouée dynamiquement int[] dans un autre objet qui va prendre soin de ces choses pour vous. Quelque chose comme un vector<int> (comme vous l'avez mentionné) ou un boost::shared_array<>.

Pour faire bouillir de ce bas - de prendre avantage de RAII dans toute la mesure, vous devriez éviter de traiter avec des pointeurs dans la mesure du possible.

Et puisque vous avez demandé pour les autres style de critiques, un mineur, c'est que lorsque vous supprimez raw pointeurs vous n'avez pas besoin de vérifier pour 0 avant d'appeler delete - delete des poignées de cas en ne faisant rien de sorte que vous n'avez pas à vous de l'encombrement de code avec les contrôles.

2voto

Jim Buck Points 10839

Vous avez besoin d'un opérateur d'affectation, de sorte que:

arrayOfAs[i] = A(3);

fonctionne comme il se doit.

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