constructors/destructors und virtual init/cleanup

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
kaiserludi
Establishment
Beiträge: 467
Registriert: 18.04.2002, 15:31

constructors/destructors und virtual init/cleanup

Beitrag von kaiserludi »

Ich denke, jeder von euch kennt folgendes Problem:

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!
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, ...

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;
    }
... 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:

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();
    }
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:

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();
    }
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.
"Mir ist auch klar, dass der Tag, an dem ZFX und Developia zusammengehen werden der selbe Tag sein wird, an dem DirectGL rauskommt."
DirectGL, endlich ist es da
:)

"According to the C++ standard, it's "undefined". That's a technical term that means, in theory, anything can happen: the program can crash, or keep running but generate garbage results, or send Bjarne Stroustrup an e-mail saying how ugly you are and how funny your mother dresses you." :shock:[/size]
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: constructors/destructors und virtual init/cleanup

Beitrag von CodingCat »

Code: Alles auswählen

class FooBase
{
public:
    FooBase(void) { my_init(); }
    virtual ~FooBase(void) { my_cleanup(); }
protected:
    virtual void init(void) { my_init(); }
    virtual void cleanup(void) { my_cleanup(); }
private:
    void my_init(void) { mpBaseBar = new int; }
    void my_cleanup(void) { delete mpBaseBar; }

    int* mpBaseBar;
};

class Foo
{
public:
    Foo(void) { my_init(); }
    virtual ~Foo(void) { my_cleanup(); }
protected:
    virtual void init(void) { FooBase::init(); my_init(); }
    virtual void cleanup(void) { my_cleanup(); FooBase::cleanup(); }
private:
    void my_init(void) { mpBar = new int; }
    void my_cleanup(void) { delete mpBar; }

    int* mpBar;
};
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
kaiserludi
Establishment
Beiträge: 467
Registriert: 18.04.2002, 15:31

Re: constructors/destructors und virtual init/cleanup

Beitrag von kaiserludi »

:)
Danke.
"Mir ist auch klar, dass der Tag, an dem ZFX und Developia zusammengehen werden der selbe Tag sein wird, an dem DirectGL rauskommt."
DirectGL, endlich ist es da
:)

"According to the C++ standard, it's "undefined". That's a technical term that means, in theory, anything can happen: the program can crash, or keep running but generate garbage results, or send Bjarne Stroustrup an e-mail saying how ugly you are and how funny your mother dresses you." :shock:[/size]
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: constructors/destructors und virtual init/cleanup

Beitrag von dot »

Wenn ich mir dein Beispiel da oben so anschau, dann hab ich allerdings stark das Gefühl, dass dein eigentliches Problem ist, dass mpBaseBar rein logisch nicht in der Basisklasse sein sollte.
kaiserludi
Establishment
Beiträge: 467
Registriert: 18.04.2002, 15:31

Re: constructors/destructors und virtual init/cleanup

Beitrag von kaiserludi »

dot hat geschrieben:Wenn ich mir dein Beispiel da oben so anschau, dann hab ich allerdings stark das Gefühl, dass dein eigentliches Problem ist, dass mpBaseBar rein logisch nicht in der Basisklasse sein sollte.
Im Anwendungsfall ist es das auch nicht, ich wollte aber ein allgemeingültiges, möglichst einfaches Beispiel, in dem ein fehlender Aufruf der Basisklasseninitialisierung nicht nur inkorrekt ist, sodnern tatsächlich zu einem Problem führt.
Warum es nun aber pauschal ein Problem und Anzeichen schlechten Designs sein soll, wenn sowohl die Basisklasse als auch die abgeleite Klasse Member haben, für die sie Speicher bei der Initialisierung allokieren, kann ich nicht nachvollziehen.
"Mir ist auch klar, dass der Tag, an dem ZFX und Developia zusammengehen werden der selbe Tag sein wird, an dem DirectGL rauskommt."
DirectGL, endlich ist es da
:)

"According to the C++ standard, it's "undefined". That's a technical term that means, in theory, anything can happen: the program can crash, or keep running but generate garbage results, or send Bjarne Stroustrup an e-mail saying how ugly you are and how funny your mother dresses you." :shock:[/size]
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: constructors/destructors und virtual init/cleanup

Beitrag von dot »

Sry, ich hab dein Beispiel wohl falsch interpretiert. Ich hatte den Eindruck, dass du da eine Basisklasse hast die Member hat, die nur wenn die Klasse Basis von bestimmten Klassen ist initialisiert werden sollen.
kaiserludi
Establishment
Beiträge: 467
Registriert: 18.04.2002, 15:31

Re: constructors/destructors und virtual init/cleanup

Beitrag von kaiserludi »

Dann hat dich dein Eindruck getäuscht. Das wollte ich gewiss nicht bezwecken und das fände ich auch selbst unschön.
"Mir ist auch klar, dass der Tag, an dem ZFX und Developia zusammengehen werden der selbe Tag sein wird, an dem DirectGL rauskommt."
DirectGL, endlich ist es da
:)

"According to the C++ standard, it's "undefined". That's a technical term that means, in theory, anything can happen: the program can crash, or keep running but generate garbage results, or send Bjarne Stroustrup an e-mail saying how ugly you are and how funny your mother dresses you." :shock:[/size]
Antworten