Falle beim Ableiten von cRecorder

  • Hallo,


    mir ist beim Ableiten von cRecorder etwas aufgefallen. cRecorder erbt von cReceiver drei virtuelle Methoden:


    Code
    virtual void Activate(bool On) {}
               	///< This function is called just before the cReceiver gets attached to
               	///< (On == true) and right after it gets detached from (On == false) a cDevice. It can be used
               	///< to do things like starting/stopping a thread.
               	///< It is guaranteed that Receive() will not be called before Activate(true).
    
    virtual void Receive(uchar *Data, int Length) = 0;
    
    
    virtual ~cReceiver();


    Der übliche Weg des Abkoppelns eines Recorders ist, dass cRecorder in seinem Destructor Detach() aufruft, dieses cDevice::Detach(), das Device in seiner Liste den Recorder raussucht und darauf Activate(false) ruft. Jetzt kommt das Problem: Wenn ich in meiner abgeleiteten Klasse Activate() überschrieben habe, kommt dieser Aufruf gar nicht bei mir an. Mein Objekt ist ja schon halb abgebaut, wenn der Destructor von cRecorder am Werk ist. Im Callstack sieht das so aus:


    [Blockierte Grafik: http://ein-eike.de/wordpress/wp-content/uploads/2014/04/virtual-method-in-desctructor.png]


    Ich habe also das virtuelle Activate() überschrieben, um die Information, dass ich deaktiviert werde, zu bekommen, habe mich auf den normalen Informationsfluss des Deaktivierens verlassen, bekomme die Information aber nie.


    Das ganze ließe sich umgehen, indem man im Destructor der abgeleiteten Klasse schon mal dasselbe macht, was in cRecorder dann nochmal passiert (in dem Fall dann vergebens, weil der Recorder schon ausgetragen wäre). Aber es kommt ziemlich überraschend und könnte Kopfzerbrechen bereiten...


    Ciao,
    Eike

  • Nicht alle auf einmal...! ;)


    Ich find das nicht schön - andererseits wird nicht jeden Tag jemand eine Klasse vom Recorder ableiten.
    Und der Umbau, das zu ändern, würde vielleicht größer werden. Ich würde saher vorschlagen,
    das überraschende Verhalten zumindest in cRecorder.h zu dokumentieren, à la


    ///< If you override Activate(), you need to call Detach() from your own constructor in order to get Activate(false)


    Oder so.

  • Nach Scott Meyers (Effective C++) sollte man im Konstruktor und Destruktor Aufrufe von virtuellen Funktionen meiden,
    er listet auch Beispiele mit Ergebnissen auf, die man so nicht erwartet.


    Ob genau so ein Fall hier auftritt, konnte ich auf der schnelle nicht ermitteln.


    Guido

  • Hallo!

    Nach Scott Meyers (Effective C++) sollte man im Konstruktor und Destruktor Aufrufe von virtuellen Funktionen meiden,
    er listet auch Beispiele mit Ergebnissen auf, die man so nicht erwartet.


    Ob genau so ein Fall hier auftritt, konnte ich auf der schnelle nicht ermitteln.

    Das ist genau sowas, aber halt "hinterrücks". Es wird eine Methode einer anderen Klasse aufgerufen,
    deren erwünschtes Verhaltes es ist, eine virtuelle Methode an der eigenen Klasse zurückzurufen.
    Es ist zwar nach C++-Standard kein undefiniertes Verhalten, aber halt überraschendes.


    Ciao,
    Eike

  • Der Aufruf von virtuellen Methoden in Konstruktor und Destruktor ist immer eine böse Falle.
    Im Konstruktor kann es gar nicht funktionieren, weil das abgeleitete Objekt zu dem Zeitpunkt noch gar nicht existiert, weshalb dann immer die Funktion in der Basisklasse aufgerufen wird.
    Und der Destruktor in der abgeleiteten Klasse ist dann eigentlich auch schon gelaufen, wenn eine Basisklasse eine virtuelle Funktion aufrufen will. Besser wäre es vermutlich, das, was auch immer im Destruktor passieren soll, in eine (in C# nennt sich das Dispose) eigene Funktion auszulagern, die sich merkt, ob sie schon mal aufgerufen wurde, oder nicht.


    Was passiert denn, wenn du den Destruktor von cRecorder direkt in deinem Destruktor aufrufst? Und ist er so aufgebaut, dass er dann beim zweiten mal sich nicht ins Knie schießt?


    Lars.

  • Was passiert denn, wenn du den Destruktor von cRecorder direkt in deinem Destruktor aufrufst? Und ist er so aufgebaut, dass er dann beim zweiten mal sich nicht ins Knie schießt?

    Also, wenn ich das Detach() selbst aufrufe, schießt sich nichts ins Knie. So hab ich's in Permashift gelöst.


    Einen Destructor händisch aufrufen hab ich nicht probiert und es rollen sich mir auch die Zehennägel auf bei dem Gedanken. ;)
    Und anscheinend nicht nur mir: http://www.parashift.com/c++-f…t-call-dtor-on-local.html


    Ciao,
    Eike

  • Einen Destructor händisch aufrufen, habe ich in der STL öfters gesehen oder in Bücher über die STL,
    hier ist darauf zu achten, dass der Destructor mehrfach aufrufbar zu machen ist. Gerade "delete" Aufrufe
    sind hier zu beachten.


    ~MyDes()
    {
    delete m_pObj;
    m_pObj = NULL; //ist dann zwingend, sonst kracht es
    }

  • Ich vermute, die verwenden Placement new.


    Ich hab keine definitive Aussage gefunden, aber meistens läuft es auf sowas hinaus:
    The destructor may also be called directly, e.g. to destroy an object
    that was constructed using placement-new or through an allocator member
    function such as std::allocator::destroy(),
    to destroy an object that was constructed through the allocator. Note
    that calling a destructor directly for an ordinary object, such as a
    local variable, invokes undefined behavior when the destructor is called
    again, at the end of scope.
    http://en.cppreference.com/w/cpp/language/destructor


    Aber es würde das Problem eh nicht lösen. Der, der ableitet, muss immer noch eingreifen,
    um das erwartete Verhalten tatsächlich zu bekommen.


    Ciao,
    Eike

Jetzt mitmachen!

Sie haben noch kein Benutzerkonto auf unserer Seite? Registrieren Sie sich kostenlos und nehmen Sie an unserer Community teil!