Im folgenden Programm beabsichtige ich, char* line
Inhalte von einem Objekt auf ein anderes durch zu kopieren strcpy
. Wenn das Programm jedoch endet, obj2
funktioniert der Destruktor von einwandfrei, der obj
Absturz jedoch nicht. gdb zeigt unterschiedliche Adressen line
für beide Objekte an.
class MyClass {
public:
char *line;
MyClass() {
line = 0;
}
MyClass(const char *s) {
line = new char[strlen(s)+1];
strcpy(line, s);
}
~MyClass() {
delete[] line;
line = 0;
}
MyClass &operator=(const MyClass &other) {
delete[] line;
line = new char[other.len()+1];
strcpy(line, other.line);
return *this;
}
int len(void) const {return strlen(line);}
};
int main() {
MyClass obj("obj");
MyClass obj2 = obj;
MyClass obj1; MyClass obj2 = obj1;
Nebenbei bemerkt , sobald Sie einen Kopierkonstruktor hinzugefügt haben : wird immer noch segfault, weil Sie aufrufen,strlen(obj1.line)
welches iststrlen(NULL)
. Wie würdeMyClass obj1; obj1.len();
.MyClass obj1; obj1.len();
Es ist undefiniertes Verhalten,strlen
einen Nullzeiger aufzurufen .Antworten:
Mit
Sie haben keine Aufgabe, Sie haben eine Kopierkonstruktion . Und Sie befolgen nicht die Regeln von drei, fünf oder null, da Sie keinen Kopierkonstruktor haben, sodass der standardmäßig generierte nur den Zeiger kopiert.
Das heißt, danach haben Sie zwei Objekte, deren
line
Zeiger beide auf genau denselben Speicher zeigen. Dies führt zu undefiniertem Verhalten, sobald eines der Objekte zerstört wird und das andere mit einem ungültigen Zeiger zurückbleibt.Die naive Lösung besteht darin, einen Kopierkonstruktor hinzuzufügen, der eine Tiefenkopie der Zeichenfolge selbst erstellt, ähnlich wie es Ihr Zuweisungsoperator tut.
Eine bessere Lösung wäre,
std::string
stattdessen für Ihre Zeichenfolgen zu verwenden und der Regel Null zu folgen.quelle
Sie müssen einen Kopierkonstruktor erstellen. Dies muss die Regel 3/5 erfüllen . Sie erstellen
obj2
, dh ein Kopierkonstruktor wird aufgerufen, nicht der Kopierzuweisungsoperator.Da Sie keinen Kopierkonstruktor haben, wird eine "flache" Kopie erstellt. Dies bedeutet, dass
line
nach Wert kopiert wird. Da es sich um einen Zeiger handelt, zeigen beideobj
undobj2
auf denselben Speicher. Der erste Destruktor wird aufgerufen und löscht diesen Speicher einwandfrei. Der zweite Konstruktor wird aufgerufen und es kommt zu einem doppelten Löschen, das Ihren Segmentierungsfehler verursacht.Wenn Sie mit C-Strings arbeiten, können Sie das Nullzeichen absolut nicht verlieren. Das Problem ist, dass es extrem leicht zu verlieren ist. Es fehlte Ihnen auch ein Selbstzuweisungsschutz in Ihrem Kopierzuweisungsoperator. Das hätte dazu führen können, dass Sie versehentlich ein Objekt zerstört haben. Ich habe ein
size_
Mitglied hinzugefügt undstrncpy()
stattdessen verwendet,strcpy()
weil es unglaublich wichtig ist, eine maximale Anzahl von Zeichen angeben zu können, wenn ein Nullzeichen verloren geht. Es wird Schäden nicht verhindern, aber es wird es mildern.Es gibt noch einige andere Dinge, die mir bei der Verwendung der Standardelementinitialisierung (ab C ++ 11) und der Verwendung einer Konstruktormitgliedsinitialisierungsliste gefallen haben . Vieles davon wird unnötig, wenn Sie es verwenden können
std::string
. C ++ kann "C mit Klassen" sein, aber es lohnt sich, sich die Zeit zu nehmen, um wirklich zu erkunden, was die Sprache zu bieten hat.Ein Konstruktor und Destruktor für Arbeitskopien ermöglicht es uns, unseren Kopierzuweisungsoperator mithilfe der "Copy and Swap Idiom" zu vereinfachen.
Link zur Erklärung .
quelle