constructors/destructors und virtual init/cleanup
Verfasst: 21.02.2012, 18:34
Ich denke, jeder von euch kennt folgendes Problem:
Der Grund dafür ist klar: Da myFoo im Gegensatz zum Rest der Zeit zum Zeitpunkt des Aufrufs des Konsturktors bzw. Destruktors von FooBase eine fooBase Instanz ist und keine Foo-Instanz, werden Aufrufe virtueller Methoden während der Konstruktion sich verhalten, als wären sie nicht virtuell, sprich, aus dem Kon-/Destruktor der Basisklasse heraus auch nur die Basisklassenimplementation der Methode aufrufen und nicht wie zur eigentlichen lebzeit des Objektes heraus die Subklassenimplementation.
Bloß, was ist die beste Praxis, damit umzugehen?
Macht man es wie folgt, ...
... dann funktionieren zwar Konstruktion und Destruktion korrekt, aber man kann init() und cleanup() nicht mehr zu Lebzeiten des Objektes aufrufen, um z.B. eine Instanz zu resetten, da man sich dabei darauf verlassen würde, dass Foo::init() sich um den Aufruf von FooBase::init() kümmert, was nun aber nicht mehr der Fall ist.
Bleib also:
Es rufen also alle Konstruktoren der Vererbungshierarchie jeweils die eigene init() auf, obwohl init die Implementation der eigenen Mutterklasse auch selbst aufruft. Ergebnis: Die init-Methode von FooBase wird in diesem Fall 2 mal aufgerufen. Würde FooBase seinerseits von FooBaseBase erben, würde die init() von FooBaseBase bereits 3 mal aufgerufen werden, usw.
Analog gilt das Gesagte für die Desturkotren und die Häufigkeit der cleanup()-Aufrufe.
Fazit:
Die einzige Lösung scheint diese zu sein:
Gibt es keinen eleganteren Weg?
PS: Eigentlich müsste init() natürlich selbst noch cleanup() aufrufen, wen man es von außerhalb des Konstruktors aufrufen können soll, ohne jedes mal vorher explizit cleanup() aufzurufen, aber da das nicht zur Kernproblematik gehört, ahbe ichs aus dem vereinfachten Beispielcode mal draußen gelassen.
Code: Alles auswählen
class FooBase
{
public:
FooBase(void);
virtual ~FooBase(void);
private:
virtual void init(void);
virtual void cleanup(void);
int* mpBaseBar;
};
Code: Alles auswählen
class Foo: public FooBase
{
public:
Foo(void);
~Foo(void);
private:
void init(void);
void cleanup(void);
int* mpBar;
};
Code: Alles auswählen
FooBase::FooBase(void)
{
init();
}
FooBase::~FooBase(void)
{
cleanup();
}
void FooBase::init(void)
{
mpBasebar = new int;
}
void FooBase::cleanup(void)
{
delete mpBaseBar;
}
Code: Alles auswählen
Foo::Foo(void)
{
}
Foo::~Foo(void)
{
}
void Foo::init(void)
{
FooBase::init();
mpBar = new int;
}
void Foo::cleanup(void)
{
delete mpBar;
FooBase::cleanup();
}
Code: Alles auswählen
Foo myFoo; // leak!
Bloß, was ist die beste Praxis, damit umzugehen?
Macht man es wie folgt, ...
Code: Alles auswählen
Foo::Foo(void)
{
init();
}
Foo::~Foo(void)
{
cleanup();
}
void Foo::init(void)
{
mpBar = new int;
}
void Foo::cleanup(void)
{
delete mpBar;
}
Bleib also:
Code: Alles auswählen
Foo::Foo(void)
{
init();
}
Foo::~Foo(void)
{
cleanup();
}
void Foo::init(void)
{
FooBase::init();
mpBar = new int;
}
void Foo::cleanup(void)
{
delete mpBar;
FooBase::cleanup();
}
Analog gilt das Gesagte für die Desturkotren und die Häufigkeit der cleanup()-Aufrufe.
Fazit:
Die einzige Lösung scheint diese zu sein:
Code: Alles auswählen
class FooBase
{
public:
FooBase(void);
virtual ~FooBase(void);
private:
virtual void init(void);
virtual void cleanup(void);
int* mpBaseBar;
};
Code: Alles auswählen
class Foo: public FooBase
{
public:
Foo(void);
~Foo(void);
private:
void init(bool calledFromConstructor=false);
void cleanup(bool calledFromDestructor=false);
int* mpBar;
};
Code: Alles auswählen
FooBase::FooBase(void)
{
init();
}
FooBase::~FooBase(void)
{
cleanup();
}
void FooBase::init(void)
{
mpBasebar = new int;
}
void FooBase::cleanup(void)
{
delete mpBaseBar;
}
Code: Alles auswählen
Foo::Foo(void)
{
init();
}
Foo::~Foo(void)
{
cleanup();
}
void Foo::init(bool calledFromConstructor)
{
if(!calledFromConstructor)
FooBase::init();
mpBar = new int;
}
void Foo::cleanup(bool calledFromDestructor)
{
delete mpBar;
if(!calledFromDestructor)
FooBase::cleanup();
}
PS: Eigentlich müsste init() natürlich selbst noch cleanup() aufrufen, wen man es von außerhalb des Konstruktors aufrufen können soll, ohne jedes mal vorher explizit cleanup() aufzurufen, aber da das nicht zur Kernproblematik gehört, ahbe ichs aus dem vereinfachten Beispielcode mal draußen gelassen.