2 Fragen ...



  • class Foo
    {
    public:
    	Foo() {}
    	virtual ~Foo() {flush();}
    	virtual void write(const Bar&) = 0;
    	virtual void flush() throw() = 0;
    }
    

    Der Beispielcode hat 2 Probleme:
    1. Linkerfehler weil flush() pure virtual ist
    2. vermutlich ein Crash beim Zerstören von abgeleiteten Klassen, da das richige flush() nicht mehr aufgerufen werden kann

    Wie kann ich das umsetzen, was der Beispielcode prinzipiell beschreibt? Also abgeleitete Klassen dazu zwingen, flush() zu überschreiben und trotzdem sicherzustellen, dass auf jeden Fall flush() aufgerufen wird?



  • eine_frage schrieb:

    class Foo
    {
    public:
    	Foo() {}
    	virtual ~Foo() {flush();}
    	virtual void write(const Bar&) = 0;
    	virtual void flush() throw() = 0;
    }
    

    Der Beispielcode hat 2 Probleme:
    1. Linkerfehler weil flush() pure virtual ist
    2. vermutlich ein Crash beim Zerstören von abgeleiteten Klassen, da das richige flush() nicht mehr aufgerufen werden kann

    Wie kann ich das umsetzen, was der Beispielcode prinzipiell beschreibt? Also abgeleitete Klassen dazu zwingen, flush() zu überschreiben und trotzdem sicherzustellen, dass auf jeden Fall flush() aufgerufen wird?

    Das Foo nenne ich mal File. Und es soll die ReadFile,WriteFile und so aus der Win-Api wrappen.

    Für mich tragfähig ist

    class StreamBuffer//geht auch für sockets, pipes, console. hierauf opertiert op<<
    {
       virtual void flush()=0;
       virtual void write(const Bar&) = 0;
       virtual ~Buffer(){
       }
       char* begin,writePos,end;
    }
    class File:public StreamBuffer
    {
       ~Foo(){
          flush();//muss leider sein, ist aber absolut zu verschmerzen
       }
       //implementiert flush&write
    }
    

    Alles zu automatisieren, riskiert man signifikant, zu viele Begriffe zu erfinden, die keine dauerhafte Bedeutung haben und nur in diesem einen Fall etwas bringen. Also daß man in fünf Jahren sich im Code gar nicht mehr zurechtfindet. Oder der neue Mitarbeiter aus dem Fenster springt. Man riskiert, den code komplett zu verboosteln.

    class StreamBuffer{
       virtual void flush()=0;
       virtual void write(const Bar&) = 0;
       virtual void warnIfUsedBarely()=0;
    }
    class FileBuffer:public StreamBuffer{
       //implementiert flush&write
    }
    template <typename AbstractStreamBuffer>
    class ConcreteStreamBuffer:public AbstractStreamBuffer{
       ConcreteStreamBuffer(){
          flush();//geht doch
       }
       void warnIfUsedBarely(){};
    }
    class File:private ConcreteStreamBuffer<FileBuffer>
    {
    }
    

    Auch mal gucken, wie std::<iostream> dabei vorgeht. Onkel Struppi war vor 20 Jahren schon erstaunlich weit.



  • Das gibt schon mal keinen Linkerfehler mehr (VS 2010 Express):

    class Foo
    {
       public:
          Foo() {}
          virtual ~Foo() {test();}
          virtual void flush() throw() = 0;
    
       private:
          void test(){flush();}
    };
    


  • Das Problem ist, dass die abgeleitete Klasse vor der Basisklasse aufgeräumt wird. Und dazu gehört, die vtable, also die Sprungtabelle für virtuelle Methoden anzupassen. Im Destruktor werden damit keine virtuellen Methoden der abgeleitete Klasse aufgerufen.

    Das macht auch Sinn, da im Destruktor die abgeleitete Klasse möglicherweise seine Member bereits zerstört hat. Dann würde eine virtuelle Methode der abgeleitete Klasse möglicherweise auf diese Member zugreifen wollen.

    Beispiel:

    class Bar : public Foo
    {
          std::ofstream* out;
       public:
          ~Bar()
          {
              delete out;
          }
          virtual void flush() throw()
          {
              out->flush();
          }
    }
    

    Würde der Destruktor von Foo Bar::flush() aufrufen, würde Bar::flush() auf out zugreifen, welches aber bereits gelöscht ist. Da hilft es auch nicht, den Aufruf vor dem Compiler zu verstecken, indem man eine test()-Methode schreibt. Das erschwert lediglich, dass der Compiler vor diesem Fehler warnen kann.

    Das gleiche gilt im übrigen für den Konstruktor. Auch dort kann man keine virtuellen Methoden der abgeleiteten Klasse aufrufen.



  • Belli schrieb:

    Das gibt schon mal keinen Linkerfehler mehr (VS 2010 Express):

    class Foo
    {
       public:
          Foo() {}
          virtual ~Foo() {test();}
          virtual void flush() throw() = 0;
          
       private:
          void test(){flush();}
    };
    

    Aber das ruft nach Ärger.
    Im Dtor von Foo ist das Bar-Objekt schon teildestruiert und kein Bar-Objekt mehr, sondern nur noch ein Foo-Objekt. Darum wird die virtuelle Funktion nicht mehr korrekt aufgerufen, also nicht im Sinne des Erfinders.

    Hab mal getestet und das macht mich ganz durcheinander.

    #include <iostream>
    using namespace std;
    
    class Foo
    {
       public:
          Foo() {}
          virtual ~Foo() {test();}
          virtual void flush()= 0;
    
       private:
          void test(){flush();}
    };
    void Foo::flush(){
            cout<<"fooflush\n";
    }
    
    class Bar{
        virtual void flush(){
            cout<<"barflush\n";
        }
    };
    
    int main(){
        cout<<"start\n";
        {
            Bar *b=new Bar;
            delete b;//flusht gar nicht? 
        }
        cout<<"fertig\n";
    }
    

  • Mod

    Ableitung von Foo vergessen.



  • Naja, leuchtet mir ein. Dass man im Konstr. keine virtuellen Methoden aufrufen kann, war klar, die abgeleiteten Klassen sind noch nicht konstruiert.

    Im Gegenzug sind sie dann im Destr. nicht mehr konstruiert ...

    Demnach lässt sich die Aufgabenstellung nicht erfüllen. Es ist stattdessen notwendig, in den abgeleiteten Klassen explizit flush() aufzurufen.



  • Alternative: Zerlege das Ganze in zwei Klassen(-hierarchien), analog zu den iostreams mit ihren streambufs. Damit sorgst du dafür, dass das was auch immer da geflusht werden muss, im DTor von Foo noch existiert:

    class Flushable {
    public: 
      virtual void flush() = 0;
    };
    
    class Foo
    {
        Flushable* f_;
    public:
        Foo(Flushable* f) : f_(f) {}
        virtual ~Foo() { f_->flush();}
    };
    


  • pumuckl schrieb:

    Alternative: Zerlege das Ganze in zwei Klassen(-hierarchien), analog zu den iostreams mit ihren streambufs. Damit sorgst du dafür, dass das was auch immer da geflusht werden muss, im DTor von Foo noch existiert:

    class Flushable {
    public: 
      virtual void flush() = 0;
    };
    
    class Foo
    {
        Flushable* f_;
    public:
        Foo(Flushable* f) : f_(f) {}
        virtual ~Foo() { f_->flush();}
    };
    

    Eine Frage, we hat hier die Verantwortung für die Lebenszeit des Flushables? Sonst könnte die abgeleitete Klasse nacher meinen, das Objekt im Destruktor zu zerstören, da währ der Sinn des ganzen dahin. Beim std::filebuf wird das Objekt im eigenen Destruktor "geflusht", der std::fstream hat ja da nichts mehr mit zu tun, besonders da sein Destruktor implizit deklariert ist.


Anmelden zum Antworten