Frage

Dies ist eine Art von Anfänger Frage, aber ich habe C ++ nicht getan in einer langen Zeit, so hier geht ...

Ich habe eine Klasse, die eine dynamisch zugewiesene Array enthält, sagt

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;
    }
}

Aber jetzt will ich eine dynamisch zugewiesene Array dieser Klassen erstellen. Hier ist mein aktueller Code:

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

Aber diese bläst furchtbar auf. Weil das neue A Objekt erstellt (mit A(3) call) wird, wenn die for Schleifeniteration endet zerstört, und dies bedeutet, dass der interne myArray dieser A Instanz erhält delete []-ed.

So denke ich, meine Syntax schrecklich falsch sein muss? Ich denke, es gibt ein paar Korrekturen sind, die wie übertrieben erscheinen, die ich bin der Hoffnung, zu vermeiden:

  • Erstellen einer Kopie Konstruktor für A.
  • Mit vector<int> und vector<A> also muss ich keine Sorgen über all dies.
  • Anstelle von arrayOfAs ein Array von A Objekte sein, haben sie eine Reihe von A* Zeiger sein.

Ich würde denken, dies ist nur einige Anfänger Sache, wo eine Syntax gibt es das tatsächlich funktioniert, wenn eine Reihe von Dingen, die dynamische Zuordnung versucht, die interne dynamische Zuordnung haben.

(Auch Stil Kritik geschätzt, da es schon eine Weile her, seit ich C ++ tat.)

Update für zukünftige Zuschauer : Alle unten sind wirklich hilfreiche Antworten. Martin ist wegen des Beispielcode akzeptiert und die nützlichen „rule of 4“, aber ich schlage vor, sie zu lesen wirklich alles. Einige sind gut, prägnante Aussagen darüber, was falsch ist, und einige weisen darauf hin, richtig, wie und warum vectors ist ein guter Weg zu gehen.

War es hilfreich?

Lösung

Für Behälter bauen Sie natürlich wollen eine der Standard-Container verwenden (wie ein std :: vector). Aber dies ist ein perfektes Beispiel für das, was Sie beachten müssen, wenn Sie Ihr Objekt RAW Zeiger enthält.

Wenn Sie Ihr Objekt einen RAW-Zeiger hat, dann müssen Sie die Regel von 3 (jetzt der Regel von 5 in C ++ 11) erinnern.

  • Constructor
  • Destructor
  • Kopieren Constructor
  • Zuordnung Operator
  • Nach Constructor (C ++ 11)
  • Verschieben Zuordnung (C ++ 11)

Das ist, weil, wenn nicht der Compiler definiert wird seine eigene Version dieser Methoden erzeugen (siehe unten). Der Compiler erzeugt Versionen sind nicht immer nützlich, wenn mit RAW-Zeiger handelt.

Der Copy-Konstruktor ist die harte ein korrekt zu erhalten (es ist nicht trivial, wenn man die starke Ausnahme Garantie zur Verfügung stellen möchten). Der Zuweisungsoperator kann in Bezug auf die Copy-Konstruktor definiert werden, wie Sie die Kopie und Swap-Idiom intern verwenden können.

Siehe unten für weitere Informationen auf das absolute Minimum für eine Klasse einen Zeiger auf ein Array von ganzen Zahlen enthält.

Zu wissen, dass es nicht trivial ist, um es Sie zu erhalten korrigieren sollte mit std :: vector betrachten, anstatt einen Zeiger auf ein Array von ganzen Zahlen. Der Vektor ist leicht zu (und erweitert) zu bedienen und deckt alle mit Ausnahmen verbundene Probleme. Vergleichen Sie die folgende Klasse mit der Definition von A unten.

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

Mit Blick auf Ihrem Problem:

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.
}

Der Compiler erzeugt Zuweisungsoperator ist für fast alle Situationen in Ordnung, aber wenn RAW Zeiger im Spiel sind, müssen Sie achten. In Ihrem Fall ist es ein Problem verursacht, weil der flache Kopie Problem. Sie haben mit zwei Objekten am Ende, die Zeiger auf das gleiche Stück Speicher enthalten. Wenn die A (3) am Ende der Schleife außerhalb des Bereichs geht nennt es delete [] auf seinem Zeiger. Somit ist die andere Objekt (in der Matrix) nun einen Zeiger auf den Speicher enthält, die an das System zurückgeführt wurde.

Der Compiler erzeugt Copykonstruktor ; Kopien jedes Mitglied Variable, indem Sie, dass die Mitglieder Konstruktor kopieren. Für Zeiger bedeutet dies, nur der Zeigerwert aus dem Quellobjekt zum Zielobjekt kopiert wird (daher flache Kopie).

Compiler generierten Zuweisungsoperator ; Kopien jedes Mitglied durch variable dass Mitglieder Zuweisungsoperator. Für Zeiger bedeutet dies, nur der Zeigerwert aus dem Quellobjekt zum Zielobjekt kopiert wird (daher flache Kopie).

So das Minimum für eine Klasse, die einen Zeiger enthält:

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 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) 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;
         }   
 }

Andere Tipps

Ich würde empfehlen, std :: vector mit: so etwas wie

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

Es ist nichts falsch mit dem leichten Übermaß von STL, und Sie werden in der Lage, mehr Zeit der Umsetzung der spezifischen Eigenschaften Ihrer App zu benutzen, statt das Rad neu zu erfinden.

Der Konstruktor Ihres Ein Objekt weist ein anderes Objekt dynamisch und speichert einen Zeiger auf die dynamisch zugewiesenen Objekt in einem Rohzeiger.

Für dieses Szenario Sie muss Ihre eigene Kopie Konstruktor definieren, Zuweisungsoperator und destructor. Der Compiler erzeugt diejenigen, wird nicht korrekt funktionieren. (Dies ist eine logische Folge der „Gesetz der Großen Drei“: A-Klasse mit einem destructor, Zuweisungsoperator, Copy-Konstruktor muss in der Regel alle 3).

Sie haben Ihren eigenen destructor definiert (und Sie erwähnte eine Kopie Konstruktor erstellen), aber Sie müssen beide der anderen zwei der drei großes definieren.

Eine Alternative ist, den Zeiger auf Ihre dynamisch zugewiesenen int[] in einem anderen Objekt zu speichern, die sich um diese Dinge für Sie brauchen. So etwas wie ein vector<int> (wie Sie bereits erwähnt) oder ein boost::shared_array<>.

Um dies einkochen -. Vorteil RAII in vollem Umfang zu übernehmen, sollten Sie vermeiden, mit rohen Zeigern so weit wie möglich zu tun

Und da man für einen anderen Stil Kritik gebeten, eine geringfügige ist, dass, wenn Sie rohe Zeiger löschen Sie müssen für 0 nicht überprüfen, bevor delete Aufruf - delete behandelt diesen Fall durch nichts zu tun, damit Sie nicht zu Unordnung haben Code, den Sie mit den Kontrollen.

  1. Verwenden Sie Array oder gemeinsame Behälter für Objekte nur, wenn sie Standard und Konstrukteuren kopieren.

  2. Shop Zeiger anders (oder Smart-Pointer, können aber einige Probleme in diesem Fall treffen).

PS: Immer definiert eigenen Standard und Kopierkonstruktoren sonst automatisch generiert wird verwendet werden

Sie benötigen einen Zuweisungsoperator, so dass:

arrayOfAs[i] = A(3);

funktioniert, wie es sollte.

Warum nicht eine Methode setSize haben.

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

Ich mag die „Kopie“, aber in diesem Fall wird der Standard-Konstruktor ist nicht wirklich etwas zu tun. Die SetSize könnten die Daten aus dem ursprünglichen m_array kopieren (falls vorhanden) .. Sie müßten die Größe des Arrays speichern innerhalb der Klasse zu tun.
OR
Die SetSize konnte die ursprüngliche m_array löschen.

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);
}

Mit der Platzierung Merkmale new Operator, können Sie das Objekt an Ort und Stelle erstellen und Kopieren vermeiden:

  

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

     

Simply gibt ptr (keine Lagerung zugeordnet ist).   Beachten Sie aber, dass, wenn die Funktion durch einen neuen Ausdruck aufgerufen wird, wird die richtige Initialisierung durchgeführt werden (für Klassenobjekte umfasst dies die Standard Konstruktor aufrufen).

Ich schlage vor, wie folgt vor:

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);
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top