Forum: Compiler & IDEs C++: delete auf Elternklasse


von Bronco (Gast)


Lesenswert?

Hallo zusammen,

ich habe eine Elternklasse, von der ich mehrere Kindklassen ableite.
Die Kindklassen haben alle eine unterschiedliche Anzahl an Variablen, 
d.h. ihre Instanzen belegen unterschiedlich viel RAM.
Mein Wunsch ist es, der Einfachheit halber möglichst oft über einen 
Elternklassen-Pointer auf die Instanzen der unterschiedlichen 
Kindklassen zuzugreifen.

Beim Anlegen einer Kindklassen-Instanz muss ich eine 
Einzelfallbehandlung machen:
1
elternklasse_t* eltklaptr;
2
if      (kindklassentyp = 0) { eltklaptr = new kindklasse_0_t; }
3
else if (kindklassentyp = 1) { eltklaptr = new kindklasse_1_t; }
4
else if (kindklassentyp = 2) { eltklaptr = new kindklasse_2_t; }
5
...

Dass Freigeben einer Kindklassen-Instanz würde ich gerne 
vereinheitlichen:
1
delete eltklaptr;

Nun habe ich im Netz folgendes gefunden:
Ist der Destructor der Elternklasse virtuel, wird bei delete mit 
Elternklassenzugriff die komplette Instanz (Elternteil und Kindteil) 
freigegeben.
Ist der Destructor der Elternklasse aber nicht virtuel, wird bei delete 
mit Elternklassenzugriff nur der Elternteil der Instanz freigegeben.

Das würde ja bedeutet, dass der Kindteil der Instanz als Speicherleiche 
zurückbleibt. Ist das richtig?

von Kai S. (zigzeg)


Lesenswert?

Nicht ganz. Wahrscheinlich wird durch die Abbildung auf den 
zugrundeliegenden malloc()/free() Mechanismus aller Speicher 
freigegeben.
Problem ist aber das die Destructoren der Daten der Kind-Objekte nicht 
aufgerufen werden. Das kann unterschiedliche Auswirkungen haben - gut 
ist das auf jeden Fall nicht !

von Daniel A. (daniel-a)


Lesenswert?

Bronco schrieb:
> if      (kindklassentyp = 0) { eltklaptr = new kindklasse_0_t; }
> else if (kindklassentyp = 1) { eltklaptr = new kindklasse_1_t; }

Vorsicht! der vergleichsoperator ist == und nicht =
Für soetwas würde ich eher arrays oder const std::map nehmen.
1
template<typename T> 
2
  T* constructor(){
3
    return new T();
4
  }
5
  typedef base*(*constructor_t)();
6
  const constructor_t constructors[] = {
7
    &constructor<derived1>,
8
    &constructor<derived2>,
9
    &constructor<derived3>
10
  };
11
  const std::size_t constructor_count = sizeof(constructors)/sizeof(constructor_t);
12
...
13
  base* eltklaptr = (*constructors[kindklassentyp])();
14
...

Bronco schrieb:
> Ist der Destructor der Elternklasse aber nicht virtuel, wird bei delete

Das ist undefiniert.

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

Bronco schrieb:
> Nun habe ich im Netz folgendes gefunden:
> ...

Deine Fundstelle im Netz ist da ungenau. Merk dir einfach anders herum: 
Willst du delete auf abgeleitete Klassen über einen Pointer auf die 
Basisklasse ausführen, muß der Destruktor der Basisklasse virtuell sein.

Einfach machen, und alles wird gut.

von Programmierer (Gast)


Lesenswert?

Daniel A. schrieb:
> Vorsicht! der vergleichsoperator ist == und nicht =
> Für soetwas würde ich eher arrays oder const std::map nehmen.
So was nennt sich factory, und so würde ich as auch im Code nennen, und 
nicht "constructor", denn ein C ++ konstruktor ist es nicht! Und statt 
Funktionspointern kann man das mit std::function schöner/universeller 
machen.

von Bronco (Gast)


Lesenswert?

Danke für die Antworten:

Was mich verwirrt hatte:

http://stackoverflow.com/questions/461203/when-to-use-virtual-destructors
---------8<---------------
Since Base's destructor is not virtual and b is a Base* pointing to a 
Derived object, delete b has undefined behaviour. In most 
implementations, the call to the destructor will be resolved like any 
non-virtual code, meaning that the destructor of the base class will be 
called but not the one of the derived class, resulting in resources 
leak.
---------8<---------------

Hier wird gesagt, dass das Nicht-Aufrufen des Kindklassen-Destructor zu 
"ressource leak" führt.
Ist logisch für den Fall, wo im Kindklassen-Destructor explizit 
Ressourcen freigegeben werden.
Aber auf den mit new reservierten Speicherplatz für die 
Kindklassen-Instanz, der nun mit delete wieder freigeben wird, hat das 
doch keinen Einfluss, oder?

von Oliver S. (oliverso)


Lesenswert?

Programmierer schrieb:
> denn ein C ++ konstruktor ist es nicht!

So ist es. So braucht man ja nicht mal mehr Vererbung.

Aber das ist ja das schöne an C++. Jeder, wie er will. Der eine machts 
old-school, der andere baut Templates.

Oliver

von Rolf M. (rmagnus)


Lesenswert?

Bronco schrieb:
> Hier wird gesagt, dass das Nicht-Aufrufen des Kindklassen-Destructor zu
> "ressource leak" führt.

Es wird gesagt, daß nicht definiert ist, was passiert. Das ist der 
wichtige Teil. Als Randnotiz wird noch erwähnt, daß es auf den meisten 
Compilern dazu führen wird, daß nur der Destruktor der Basisklasse 
aufgerufen wird.

> Ist logisch für den Fall, wo im Kindklassen-Destructor explizit
> Ressourcen freigegeben werden.

Es passiert vermutlich auch in dem Fall, indem die Kindklasse wieder 
Objekte mit Destruktoren enthält, die beim Zerstören eines Objekts der 
Kindklasse implizit mit aufgerufen werden.

> Aber auf den mit new reservierten Speicherplatz für die
> Kindklassen-Instanz, der nun mit delete wieder freigeben wird, hat das
> doch keinen Einfluss, oder?

Spielt eigentlich keine Rolle. Wie gesagt: Das wichtige ist, daß das 
Verhalten undefiniert ist und damit erstens vom Compiler (und notfalls 
auch der Mondphase) abhängig sein kann und zweitens grundsätzlich zu 
vermeiden ist.

von Bronco (Gast)


Lesenswert?

Rolf Magnus schrieb:
> Es wird gesagt, daß nicht definiert ist, was passiert. Das ist der
> wichtige Teil.

Okay, verstanden.
Dann bin ich aber echt froh, dass der GCC eine Warnung ausgibt, denn von 
allein wäre ich da nicht drauf gekommen.

von Programmierer (Gast)


Lesenswert?

Oliver S. schrieb:
> So ist es. So braucht man ja nicht mal mehr Vererbung.
Doch, braucht man wohl. Die Klassen, von denen Instanzen angelegt werden 
sollen, müssen eine gemeinsame Basisklasse haben, die dann als 
Rückgabetyp der Factory-Funktionen verwendet wird. Was willst du als 
Rückgabetyp verwenden, wenn es keine gemeinsame Basisklasse gibt? Und 
wie virtuelle Funktionen aufrufen?

von Oliver S. (oliverso)


Lesenswert?

Das ist ja das "schöne" an Templates. Lauter völlig unabhängige Klassen, 
die Zillionen Kombinationsmöglichkeiten verwaltet der Compiler.

Oliver

von Programmierer (Gast)


Lesenswert?

Oliver S. schrieb:
> Das ist ja das "schöne" an Templates. Lauter völlig unabhängige Klassen,
> die Zillionen Kombinationsmöglichkeiten verwaltet der Compiler.
Das bringt dir leider bei Factory-Funktionen gar nichts. Gib mir mal ein 
konkretes Code-Beispiel wie du das hier ohne Vererbung hinbekommen 
willst:

https://ideone.com/h2xEbm

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.