
Dominik
Ich habe eine Klasse, die ein dynamisch zugewiesenes Array enthält, sagen wir
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 möchte ich ein dynamisch zugewiesenes Array dieser Klassen erstellen. Hier mein aktueller Code:
A* arrayOfAs = new A[5];
for (int i = 0; i < 5; ++i)
{
arrayOfAs[i] = A(3);
}
Aber das explodiert fürchterlich. Denn das Neue A
erstelltes Objekt (mit der A(3)
Anruf) wird zerstört, wenn die for
Schleifeniteration endet, und das bedeutet, dass die internal myArray
davon A
Instanz bekommt delete []
-ed.
Also denke ich, dass meine Syntax schrecklich falsch sein muss? Ich denke, es gibt ein paar Fixes, die wie ein Overkill erscheinen, den ich hoffentlich vermeiden möchte:
- Erstellen eines Kopierkonstruktors für
A
.
- Verwenden
vector<int>
und vector<A>
also muss ich mich um all das nicht kümmern.
- Anstatt zu haben
arrayOfAs
ein Array von sein A
Objekte, sei ein Array von A*
Zeiger.
Ich würde denken, dass dies nur eine Anfängersache ist, bei der es eine Syntax gibt, die tatsächlich funktioniert, wenn versucht wird, ein Array von Dingen mit interner dynamischer Zuweisung dynamisch zuzuweisen.
(Auch Stilkritik ist willkommen, da es schon eine Weile her ist, seit ich C++ gemacht habe.)
Update für zukünftige Zuschauer: Alle Antworten unten sind wirklich hilfreich. Martins wird wegen des Beispielcodes und der nützlichen „4er-Regel“ akzeptiert, aber ich empfehle wirklich, sie alle zu lesen. Einige sind gute, prägnante Aussagen darüber, was falsch ist, und einige weisen richtig darauf hin, wie und warum vector
s sind ein guter Weg.
Zum Erstellen von Containern möchten Sie natürlich einen der Standardcontainer verwenden (z. B. einen std::vector). Dies ist jedoch ein perfektes Beispiel für die Dinge, die Sie berücksichtigen müssen, wenn Ihr Objekt RAW-Zeiger enthält.
Wenn Ihr Objekt einen RAW-Zeiger hat, müssen Sie sich an die 3er-Regel erinnern (jetzt die 5er-Regel in C++11).
- Konstrukteur
- Destruktor
- Konstruktor kopieren
- Aufgabenverwalter
- Move-Konstruktor (C++11)
- Zuweisung verschieben (C++11)
Dies liegt daran, dass der Compiler, wenn er nicht definiert ist, seine eigene Version dieser Methoden generiert (siehe unten). Die vom Compiler generierten Versionen sind nicht immer nützlich, wenn es um RAW-Zeiger geht.
Der Kopierkonstruktor ist schwer zu korrigieren (es ist nicht trivial, wenn Sie die starke Ausnahmegarantie bereitstellen möchten). Der Zuweisungsoperator kann in Bezug auf den Kopierkonstruktor definiert werden, da Sie das Kopieren- und Austauschen-Idiom intern verwenden können.
Unten finden Sie alle Details zum absoluten Minimum für eine Klasse, die einen Zeiger auf ein Array von Ganzzahlen enthält.
Da Sie wissen, dass es nicht trivial ist, es richtig zu machen, sollten Sie in Betracht ziehen, std::vector anstelle eines Zeigers auf ein Array von Ganzzahlen zu verwenden. Der Vektor ist einfach zu verwenden (und zu erweitern) und deckt alle Probleme ab, die mit Ausnahmen verbunden sind. 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) {}
};
Betrachte dein 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 vom Compiler generierte Zuweisungsoperator ist für fast alle Situationen in Ordnung, aber wenn RAW-Zeiger im Spiel sind, müssen Sie aufpassen. In Ihrem Fall verursacht es ein Problem wegen der seichte Kopie Problem. Sie haben am Ende zwei Objekte, die Zeiger auf denselben Speicher enthalten. Wenn A(3) am Ende der Schleife den Gültigkeitsbereich verlässt, ruft es delete auf [] auf seinem Zeiger. Somit enthält das andere Objekt (im Array) nun einen Zeiger auf den Speicher, der an das System zurückgegeben wurde.
Der vom Compiler generierte Kopierkonstruktor; kopiert jede Member-Variable mit diesem Member-Kopierkonstruktor. Für Zeiger bedeutet dies nur, dass der Zeigerwert vom Quellobjekt zum Zielobjekt kopiert wird (daher flache Kopie).
Der vom Compiler generierte Zuweisungsoperator; kopiert jede Member-Variable mit diesem Member-Zuweisungsoperator. Für Zeiger bedeutet dies nur, dass der Zeigerwert vom Quellobjekt zum Zielobjekt kopiert wird (daher flache Kopie).
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(©.mArray[0],©.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;
}
}
Ich würde empfehlen, std::vector: so etwas wie zu verwenden
typedef std::vector<int> A;
typedef std::vector<A> AS;
Gegen den leichten Overkill von STL ist nichts auszusetzen, und Sie können mehr Zeit damit verbringen, die spezifischen Funktionen Ihrer App zu implementieren, anstatt das Fahrrad neu zu erfinden.
Der Konstruktor Ihres A-Objekts weist dynamisch ein anderes Objekt zu und speichert einen Zeiger auf dieses dynamisch zugeordnete Objekt in einem Rohzeiger.
Für dieses Szenario, Sie Muss Definieren Sie Ihren eigenen Kopierkonstruktor, Zuweisungsoperator und Destruktor. Die vom Compiler generierten werden nicht korrekt funktionieren. (Dies ist eine Folge des „Gesetzes der Großen Drei“: Eine Klasse mit Destruktor, Zuweisungsoperator oder Kopierkonstruktor benötigt im Allgemeinen alle 3).
Sie haben Ihren eigenen Destruktor definiert (und Sie erwähnten das Erstellen eines Kopierkonstruktors), aber Sie müssen die beiden anderen der großen Drei definieren.
Eine Alternative besteht darin, den Zeiger auf Ihren dynamisch zugewiesenen zu speichern int[]
in einem anderen Objekt, das diese Dinge für Sie erledigt. So etwas wie ein vector<int>
(wie du schon erwähnt hast) oder a boost::shared_array<>
.
Um es auf den Punkt zu bringen – um RAII in vollem Umfang nutzen zu können, sollten Sie den Umgang mit rohen Zeigern so weit wie möglich vermeiden.
Und da Sie nach anderen Stilkritiken gefragt haben, ist eine kleine, dass Sie beim Löschen von Rohzeigern vor dem Aufrufen nicht nach 0 suchen müssen delete
– delete
behandelt diesen Fall, indem es nichts tut, damit Sie Ihren Code nicht mit den Überprüfungen überladen müssen.
-
Verwenden Sie Arrays oder allgemeine Container nur dann für Objekte, wenn sie über Standard- und Kopierkonstruktoren verfügen.
-
Speichern Sie Zeiger anderweitig (oder intelligente Zeiger, aber in diesem Fall können einige Probleme auftreten).
PS: Immer eigenen Default definieren und Konstruktoren kopieren, sonst werden automatisch generierte verwendet

Jim Buck
Sie benötigen einen Zuweisungsoperator, damit:
arrayOfAs[i] = A(3);
funktioniert wie es soll.

Martin York
Warum nicht eine setSize-Methode 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 macht der Standardkonstruktor nicht wirklich etwas. Die SetSize könnte die Daten aus dem ursprünglichen m_array kopieren (falls vorhanden). Dazu müssten Sie die Größe des Arrays in der Klasse speichern.
ODER
Die SetSize könnte das 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);
}

Saman Bargi
Verwenden Sie die Platzierungsfunktion von new
Operator können Sie das Objekt an Ort und Stelle erstellen und das Kopieren vermeiden:
Platzierung (3) :void* operator new (std::size_t size, void* ptr) noexcept;
Gibt einfach ptr zurück (es wird kein Speicherplatz zugewiesen). Beachten Sie jedoch, dass, wenn die Funktion von einem neuen Ausdruck aufgerufen wird, die richtige Initialisierung durchgeführt wird (für Klassenobjekte umfasst dies den Aufruf ihres Standardkonstruktors).
Ich schlage Folgendes 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);
}
9893100cookie-checkDynamisches Zuordnen eines Arrays von Objektenyes