ich habe mich am C++ Quiz auf http://www.netrino.com/ versucht.
Dort gibt es eine Frage, bei der meine Antwort von der
erwarteteb L�sung abweicht. Ich bin aber der �berzeugung,
dass meine Antwort richtig ist. Wer hat nun recht?
Q: What will happen in the following code after the=20
function foo() returns?
//======== Quiz Code ==========
class base
{
public:
base() { }
~base() { }
};
class derived : public base
{
private:
int *p_pi_values;
public:
derived() : p_pi_values(new int[100]) { }
~derived() { delete [] p_pi_values; }
};
void foo(void)
{
derived *p_derived = new derived();
base *p_base = p_derived;
// Do some other stuff here.
delete p_base;
}
//======= End Quiz Code ============
M�gliche Antworten:
1: The integer array is guaranteed to be properly deleted
2: The integer array will not be properly deleted
3: Nothing. This will not compile
4: The behavior is undefined
Nach meiner Meinung war die Antwort 2 richtig, da der
Destruktor von Base nicht virtuell ist.
Die angeblich richtige Antwort ist aber 4.
Warum sollte das Verhalten undefiniert sein?
- Heinz
Heinz Saathoff <news...@arcor.de> writes:
> Mögliche Antworten:
> 1: The integer array is guaranteed to be properly deleted
> 2: The integer array will not be properly deleted
> 3: Nothing. This will not compile
> 4: The behavior is undefined
>
> Nach meiner Meinung war die Antwort 2 richtig, da der
> Destruktor von Base nicht virtuell ist.
> Die angeblich richtige Antwort ist aber 4.
> Warum sollte das Verhalten undefiniert sein?
Weil das der Standard so sagt. ;-)
Aus ISO/IEC 14882:1998, 5.3.5 Delete:
---
3 In the first alternative (delete object), if the static type of the
operand is different from its dynamic type, the static type shall be a
base class of the operand’s dynamic type and the static type shall
have a virtual destructor or the behavior is undefined.
---
-Stefan
> ich habe mich am C++ Quiz auf http://www.netrino.com/ versucht.
> Dort gibt es eine Frage, bei der meine Antwort von der
> erwarteteb Lösung abweicht. Ich bin aber der Überzeugung,
> dass meine Antwort richtig ist. Wer hat nun recht?
>
>
> Q: What will happen in the following code after the=20
> function foo() returns?
>
> //======== Quiz Code ==========
> class base
> {
> public:
> base() { }
> ~base() { }
> };
>
> class derived : public base
> {
> private:
> int *p_pi_values;
>
> public:
> derived() : p_pi_values(new int[100]) { }
> ~derived() { delete [] p_pi_values; }
> };
>
> void foo(void)
> {
> derived *p_derived = new derived();
> base *p_base = p_derived;
>
> // Do some other stuff here.
>
> delete p_base;
> }
> //======= End Quiz Code ============
>
> Mögliche Antworten:
> 1: The integer array is guaranteed to be properly deleted
> 2: The integer array will not be properly deleted
> 3: Nothing. This will not compile
> 4: The behavior is undefined
>
> Nach meiner Meinung war die Antwort 2 richtig, da der
> Destruktor von Base nicht virtuell ist.
> Die angeblich richtige Antwort ist aber 4.
> Warum sollte das Verhalten undefiniert sein?
Ich hätte auch auf 2 getippt, aber nach längerer Suche im Standard habe ich
nun die passende Stelle gefunden in 5.3.5, Absatz 3:
In the first alternative (delete object), if the static type of the operand
is different from its dynamic type, the static type shall be a base class of
the operand’s dynamic type and the static type shall have a virtual
destructor or the behavior is undefined.
Statischer und dynamischer Typ beim Delete unterscheiden sich, aber wir
haben keinen virtuellen Destruktor, also ist es undefiniert, wobei
derived::p_pi_values dabei gar keine Rolle spielt.
Nach dem was ich hier und anderswo so gelesen habe, könnte der Grund für
diese Regelung darin liegen, dass der Compiler eigenen Code zum Aufräumen
von Objekten einfach an die Destruktoren anhängen darf und sich dann darauf
verlassen kann, dass dieser auch tatsächlich aufgerufen wird. Beim obigen
Beispiel wird der Destruktor von p_derived jedoch nie aufgerufen, so dass
ein solcher Compiler Probleme bekommen könnte und zwar ganz unabhängig
davon, ob derived::p_pi_values ordentlich vernichtet wird.
Gruß
Björn
Björn Hendriks <he...@nurfuerspam.de> writes:
> Nach dem was ich hier und anderswo so gelesen habe, könnte der Grund für
> diese Regelung darin liegen, dass der Compiler eigenen Code zum Aufräumen
> von Objekten einfach an die Destruktoren anhängen darf und sich dann darauf
> verlassen kann, dass dieser auch tatsächlich aufgerufen wird. Beim obigen
> Beispiel wird der Destruktor von p_derived jedoch nie aufgerufen, so dass
> ein solcher Compiler Probleme bekommen könnte und zwar ganz unabhängig
> davon, ob derived::p_pi_values ordentlich vernichtet wird.
Interessant wird es bei Mehrfachvererbung, z.B.:
---- %< ---- SNIP ---- %< ----
struct A
{
int a;
~A() {}
};
struct B
{
int b;
~B() {}
};
struct C : public A, public B
{
int c;
~C() {}
};
int main()
{
C* pC = new C;
B* pB = pC;
delete pB; // RUMMS (jedenfalls mit gcc-4.3.2)
}
---- >% ---- SNAP ---- >% ----
Ohne virtuellen Destruktor geht das Freigeben des Speichers in die Hose,
da von B::~B() die "richtige" Startadresse des Gesamtobjekts nicht
ermittelt werden kann (weil in B::~B() keine Informationen darüber
vorliegen, dass dieses B-Objekt durch eine Vererbungshierarchie in ein
größeres Gesamtobjekt eingebettet ist).
-Stefan
> [Beispiel Mehrfachvererbung und delete]
>
>Ohne virtuellen Destruktor geht das Freigeben des Speichers in die Hose,
>da von B::~B() die "richtige" Startadresse des Gesamtobjekts nicht
>ermittelt werden kann (weil in B::~B() keine Informationen dar�ber
>vorliegen, dass dieses B-Objekt durch eine Vererbungshierarchie in ein
>gr��eres Gesamtobjekt eingebettet ist).
Eigendlich d�rfte deshalb die in
http://www.dietmar-kuehl.de/mirror/c++-faq/freestore-mgmt.html (16.9)
gemachte Umsetzung von delete nicht stimmen. Dort wird
delete p;
als �quivalent zu
if(p) {
p->~P();
operator delete(p);
}
beschrieben.
Bei dieser Umsetzung findet aber keinerlei Anpassung des Pointers p
statt. Wenn die in der FAQ genannte Umsetzung gelten w�rde, w�re meine
Antort 2 auf die Quizfrage OK gewesen. Es findet aber, wie das Beispiel
Mehrfachvererbung zeigt, etwas mehr statt.
- Heinz
[schnipp]
> > Mögliche Antworten:
> > 1: The integer array is guaranteed to be properly deleted
> > 2: The integer array will not be properly deleted
> > 3: Nothing. This will not compile
> > 4: The behavior is undefined
> >
> > Nach meiner Meinung war die Antwort 2 richtig, da der
> > Destruktor von Base nicht virtuell ist.
> > Die angeblich richtige Antwort ist aber 4.
> > Warum sollte das Verhalten undefiniert sein?
Das steht nun einmal so im Standard. Ich selbst habe auch keine Kopie
davon. Es gibt aber einen sehr frühen C++0x Entwurf, N1804 [1], der
größtenteils Deckungsgleich mit dem C++03 Standard sein sollte. Dort
steht in 5.3.5/3 (Kapitel 5.3.5, Abschnitt 3):
"... if the static type of the operand is different from its dynamic
type, the static type shall be a base class of the operand's dynamic
type and the static type shall have a virtual destructor or the
behaviour is undefined..."
[1] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1804.pdf
Gruß,
SG
> Stefan Große Pawig schrieb:
>
>> [Beispiel Mehrfachvererbung und delete]
>>
>>Ohne virtuellen Destruktor geht das Freigeben des Speichers in die Hose,
>>da von B::~B() die "richtige" Startadresse des Gesamtobjekts nicht
>>ermittelt werden kann (weil in B::~B() keine Informationen darüber
>>vorliegen, dass dieses B-Objekt durch eine Vererbungshierarchie in ein
>>größeres Gesamtobjekt eingebettet ist).
>
> Eigendlich dürfte deshalb die in
> http://www.dietmar-kuehl.de/mirror/c++-faq/freestore-mgmt.html (16.9)
> gemachte Umsetzung von delete nicht stimmen. Dort wird
>
> delete p;
> als äquivalent zu
> if(p) {
> p->~P();
> operator delete(p);
> }
> beschrieben.
>
> Bei dieser Umsetzung findet aber keinerlei Anpassung des Pointers p
> statt. Wenn die in der FAQ genannte Umsetzung gelten würde, wäre meine
> Antort 2 auf die Quizfrage OK gewesen. Es findet aber, wie das Beispiel
> Mehrfachvererbung zeigt, etwas mehr statt.
Wenn ich den Standard richtig lese, ist obiges allenfalls eine mögliche
Implementation. Laut Standard muss delete lediglich den passenden Destruktor
aufrufen und dann den Speicher freigeben, der zu dem übergebenen Pointer
gehört, wobei der Pointer von einem vorherigen Aufruf von new stammen muss
(außer es ist der Null-Pointer, bei dem delete gar keinen Effekt haben
darf).
Woher delete nun weiß welcher und wieviel Speicher freizugeben ist, bleibt
demnach der Implementation überlassen. Der Compiler kann es also aus dem Typ
des übergebenen Pointers ableiten, muss es aber nicht. Alternativ könnte new
auch in eine Tabelle schreiben, wieviel Speicher zu einem Pointer allokiert
wurde und delete sich danach richten.
Oder liege ich mit dieser Interpretation des Standards falsch?
Gruß
Björn
Ja, in der FAQ ist die Sache wohl sehr vereinfacht dargestellt - obiger Ersatzcode trifft nur zu, solange kein Polymorphismus
ins Spiel kommt. Ein weiterer Fall, der ohne virtuellen Destruktor wahrscheinlich kaum korrekt behandelt wird, ist die
�berladung des "operator delete" (i.d.R. passend zu einem ebenfalls �berladenen "operator new") in der abgeleiteten Klasse. Der
richtige "operator delete" muss ja abh�ngig vom dynamischen Typ des aufzur�umenden Objekts ausgew�hlt werden.
MfG
Falk
Heinz Saathoff <news...@arcor.de> writes:
> Eigendlich dürfte deshalb die in
> http://www.dietmar-kuehl.de/mirror/c++-faq/freestore-mgmt.html (16.9)
> gemachte Umsetzung von delete nicht stimmen. Dort wird
>
> delete p;
> als äquivalent zu
> if(p) {
> p->~P();
> operator delete(p);
> }
> beschrieben.
In der FAQ steht ja auch nur, dass der vom Compiler generierte Code
"functionally similar" (also ähnlich) ist; von "äquivalent" ist dort
nicht die Rede.
Wenn ein virtueller Destruktor vorliegt, greifen hier Mechanismen, die
sich nicht als normaler C++-Code hinschreiben lassen. In diesem Fall
sieht die Prozedur eher so aus:
--- SNIP ---
if (p) {
// neu: Typanpassung
D = dynamic_type(p); // gibt es so nicht
D* d = dynamic_cast<D*>(p);
// wie gehabt:
d->~D();
operator delete(d);
}
--- SNIP ---
Die tatsächliche Umsetzung der beiden neuen Zeilen erfolgt dabei durch
einen Blick in die vtable (oder welchen Mechanismus der Compiler auch
immer dafür vorsieht).
-Stefan
[...]
> Eigendlich dürfte deshalb die
> inhttp://www.dietmar-kuehl.de/mirror/c++-faq/freestore-mgmt.html(16.9)
> gemachte Umsetzung von delete nicht stimmen. Dort wird
> delete p;
> als äquivalent zu
> if(p) {
> p->~P();
> operator delete(p);
> }
> beschrieben.
Es geht nur um ein grobes Umschreiben, um den Unterschied
zwischen dem delete-Ausdruck und der operator delete Funktion zu
erleuchten. Es wird nicht behauptet, dass es alle Feinheiten
abdeckt.
> Bei dieser Umsetzung findet aber keinerlei Anpassung des
> Pointers p statt. Wenn die in der FAQ genannte Umsetzung
> gelten würde, wäre meine Antort 2 auf die Quizfrage OK
> gewesen. Es findet aber, wie das Beispiel Mehrfachvererbung
> zeigt, etwas mehr statt.
Freilich. In vielen Implementierung befindet sich der Aufruf des
operator delete-Funktion tatsächlich im Destruktor, da so viel
vom meist abgeleiteter Klasse abhängt. (Denk mal daran, was
passiert, wenn die abgeleitete Klasse einen klassenspezifischen
operator delete enthält, z.B.)
--
James Kanze