Wann rein virtuelle Funktionen?



  • Hi,

    ich arbeite gerade an einer Library. Die Library hat eine rein virtuelle Basisklasse. Die ist rein virtuell, weil sie eher als Vorlage für Klassen dienen soll, die von ihr erben. Wenn ein Programmierer diese Library nuten will kann er von der rein virtuellen Basisklasse ableiten. Praktischerweise sagt ihm der Kompiler, wenn er eine rein virtuelle Funktion vergessen hat zu implementieren.

    Meine Frage lautet nun: In welchen Fällen ist es sinnvoll solche Funktionen rein virtuell zu machen? Natürlich hilft so etwas dem Programmierer, der die Library nutzt. Aber wenn ich nun in der rein virtuellen Basisklasse Funktionen habe, die in geerbeten Funktionen fast immer das gleiche tun sollen, ist es dann empfehlenswerter, die Funktion in der Basisklasse schon auszuführen (so dass, wenn der Programmierer nun doch eine Änderung wünscht, er die Funktion überschreiben müsste)?

    Greetz



  • voidpointer schrieb:

    Meine Frage lautet nun: In welchen Fällen ist es sinnvoll solche Funktionen rein virtuell zu machen?

    Eigentlich immer. Die Frage müsste andersherum lauten, nämlich, wann es sinnvoll ist, überhaupt eine Standardimplementierung bereitzustellen. Und die Antwort ist: Nur dann, wenn man davon ausgeht, dass diese nicht ersetzt wird. Sonst könnte man sich den Code nämlich auch sparen.

    Wenn man dem Benutzer die Möglichkeit geben will, die vorhandene Semantik zu modifizieren, statt sie komplett auszutauschen, sollte man sich überlegen, dies durch Anwendung des Schablonenmethodenmusters (Template Method Pattern) zu tun.

    Merke: Methoden, die virtuell sind, sollten idR pur virtuell sein. Alle anderen Methoden sollten idR nicht virtuell sein.



  • Konrad Rudolph schrieb:

    Merke: Methoden, die virtuell sind, sollten idR pur virtuell sein. Alle anderen Methoden sollten idR nicht virtuell sein.

    Gibts da auch eine Begruendung?

    Wenn es ein sinnvolles Standardverhalten gibt, dann sollte man das implementieren. Wenn es keins gibt, dann pure virtual.

    Und Template Method hat damit auch irgendwie erstmal garnix zu tun...



  • Konrad Rudolph schrieb:

    ...Merke: Methoden, die virtuell sind, sollten idR pur virtuell sein. Alle anderen Methoden sollten idR nicht virtuell sein.

    Also ich kenne den Satz so ähnlich für Basisklassen ("Nur Blattklassen sollten Implementierung aufweisen") ...
    Außerdem habe ich mal gelernt, dass sich "virtuell" "weitervererbt" (auch, wenn man es nicht mehr dazu zu schreiben braucht) - so gesehen könnte man die Vorgabe sowieso nicht umsetzen (wenn man nicht ausschließlich nutzlose Klassen haben möchte) 😉

    Bei der Gelegenheit: Wird C++0x eigentlich ein "final-Feature" bieten, mit dem weitere Vererbung unterbunden wird ? Oder eine "de-virtualisierung" von Memberfunktionen ?

    Gruß,

    Simon2



  • Shade Of Mine schrieb:

    Konrad Rudolph schrieb:

    Merke: Methoden, die virtuell sind, sollten idR pur virtuell sein. Alle anderen Methoden sollten idR nicht virtuell sein.

    Gibts da auch eine Begruendung?

    Ich hatte gehofft, die wäre in meinem Posting impliziert. 😕

    Aber sei's drum, es geht letztendlich um Kapselung und darum, möglichst wenige Modifikationspunkte offenzulassen. Was der Anwender nicht zu ändern braucht, das soll er auch nicht ändern können.

    Das habe ich mir auch nicht aus den Fingern gesaugt, das steht ziemlich verbatim so in jedem Design-Pattern-Buch drin, und Meyers erwähnt es AFAIR auch irgendwo.

    (EDIT: Meyers nennt es NVI: Non-virtual method idiom.)

    Wenn es ein sinnvolles Standardverhalten gibt, dann sollte man das implementieren.

    Ja, aber diese Methode muss man idR dann nicht virtuell machen.

    Und Template Method hat damit auch irgendwie erstmal garnix zu tun...

    Doch, natürlich, denn Schablonenmethoden geben Dir das Werkzeug in die Hand, eine Standardimplementierung entsprechend anzupassen, *ohne* die gesamte Methode überschreiben zu müssen. – Beispiel:

    // Alternative 1:
    
    struct Base {
        virtual void DoStuff() { /* Default implementation. */ }
    };
    
    struct Derived : Base {
        void DoStuff() {
            // Perform additional check.
            Base::DoStuff();
        }
    };
    
    // Alternative 2:
    
    struct Base {
        void DoStuff() {
            PerformPreparatoryWork();
            // Default implementation.
        }
    
    protected:
        virtual void PerformPreparatoryWork() { }
    };
    
    struct Derived : Base {
    protected:
        void PerformPreparatoryWork() { /* Perform additional check. */ }
    };
    

    Hier ist natürlich zu beachten, dass ich in der Tat vorher Quatsch geschrieben habe, da Schablonenmethoden nicht pur virtuell sein können sondern eine leere Standardimplementierung haben.

    (EDIT: sie können natürlich doch pur virtuell sein, müssen es aber nicht.)

    Beachte auch, dass ich überall „in der Regel“ drangeschrieben habe. Natürlich gibt es sinnvolle Ausnahmen.



  • Simon2 schrieb:

    Konrad Rudolph schrieb:

    ...Merke: Methoden, die virtuell sind, sollten idR pur virtuell sein. Alle anderen Methoden sollten idR nicht virtuell sein.

    Also ich kenne den Satz so ähnlich für Basisklassen ("Nur Blattklassen sollten Implementierung aufweisen") ...

    Na ja, abgewandelt: „Nur Blattklassen sollten konkrete Klassen sein“ => Basisklassen sollten nicht direkt instanzierbar sein. Implementierungen für einige Methoden dürfen sie aber trotzdem enthalten. So steht's u.a. im Meyers.

    Bei der Gelegenheit: Wird C++0x eigentlich ein "final-Feature" bieten, mit dem weitere Vererbung unterbunden wird ? Oder eine "de-virtualisierung" von Memberfunktionen ?

    Also, vom technischen Standpunkt her ist eine Devirtualisierung ja gar nicht möglich. .NET bietet das zwar trotzdem an, es bringt aber IMHO keinen Gewinn. Ich würde sogar soweit gehen und behaupten, dass es ein Antipattern ist, da es die Basisklassenschnittstelle verändert.

    Dazu, was C++0x können wird, kann ich nichts sagen.



  • Konrad Rudolph schrieb:

    Hier ist natürlich zu beachten, dass ich in der Tat vorher Quatsch geschrieben habe, da Schablonenmethoden nicht pur virtuell sein können sondern eine leere Standardimplementierung haben.

    (EDIT: sie können natürlich doch pur virtuell sein, müssen es aber nicht.)

    Eben und damit hat man nur noch selten pure virtual funktionen 😉

    template method ist nett, hat mit virtuell <-> pure virtual aber nichts zu tun.



  • Konrad Rudolph schrieb:

    ...
    (EDIT: Meyers nennt es NVI: Non-virtual method idiom.)
    ...

    Ähhh, "NVI" bedeutet aber "non virtual interface"-Idiom und Herb Sutter plädiert dort dafür, dass das public interface keine virtuellen Funktionen enthalten sollte.

    Das ist doch eine ziemlich andere Aussage als

    Konrad Rudolph schrieb:

    ...Methoden, die virtuell sind, sollten idR pur virtuell sein. Alle anderen Methoden sollten idR nicht virtuell sein....

    Ebenso mit

    Konrad Rudolph schrieb:

    ...Basisklassen sollten nicht direkt instanzierbar sein. ...

    (was ich auch kenne und auch nachvollziehen kann)
    Ist einfach eine andere Aussage - ich habe sie nur angeführt, weil ich vermutete, dass Du sie bei Deinem obigen Statement im Hinterkopf hattest.

    Gruß,

    Simon2.



  • Simon2 schrieb:

    Konrad Rudolph schrieb:

    ...
    (EDIT: Meyers nennt es NVI: Non-virtual method idiom.)
    ...

    Ähhh, "NVI" bedeutet aber "non virtual interface"-Idiom

    … Ja, meinte ich; Schreibfehler …

    und Herb Sutter plädiert dort dafür, dass das public interface keine virtuellen Funktionen enthalten sollte.

    Das ist doch eine ziemlich andere Aussage als

    Konrad Rudolph schrieb:

    ...Methoden, die virtuell sind, sollten idR pur virtuell sein. Alle anderen Methoden sollten idR nicht virtuell sein....

    Na ja. Erst einmal ist das Ziel dasselbe, nämlich die Schnittstelle sauberzuhalten und dem Ableiter eine möglichst kleine, und dafür gezielte, Angriffsfläche zu bieten.

    Und dann ist (IMHO) Herb Sutters Ansatz eine Extremposition von NVI, und der von mir referierte Standpunkt eben eine abgeschwächte Form.



  • Konrad Rudolph schrieb:

    ...
    … Ja, meinte ich; Schreibfehler …

    Darauf wollte ich gar nicht hinaus (das wäre selbst mir zu kleinlich 😉 ), sondern darauf, dass es bei der NVI IMHO um etwas ganz Anderes geht.

    Mal Andersherum: Wie würdest Du denn Dein Statement

    Konrad Rudolph schrieb:

    ...Methoden, die virtuell sind, sollten idR pur virtuell sein. Alle anderen Methoden sollten idR nicht virtuell sein....

    praktisch umsetzen ?
    Vielleicht komme ich so zu einem Verständnis, was Du mit dieser Aussage meinst.
    So wie ich es verstehe, wäre es gar nicht umsetzbar - und meine Erfahrung mit Dir in diesem Forum legt mir nahe, dass Du das nicht meinst.

    Gruß,

    Simon2.



  • Simon2 schrieb:

    Konrad Rudolph schrieb:

    ...
    … Ja, meinte ich; Schreibfehler …

    Mal Andersherum: Wie würdest Du denn Dein Statement

    Konrad Rudolph schrieb:

    ...Methoden, die virtuell sind, sollten idR pur virtuell sein. Alle anderen Methoden sollten idR nicht virtuell sein....

    praktisch umsetzen ?
    Vielleicht komme ich so zu einem Verständnis, was Du mit dieser Aussage meinst.
    So wie ich es verstehe, wäre es gar nicht umsetzbar

    Vielleicht bewertest Du diese Aussage auch einfach als zu absolut. Sie ist ja nur eine Richtlinie. Dass man damit allein nicht auskommt, ist klar.

    Außerdem schrieb ich ja bereits, dass das nicht ganz korrekt ist, weil in vielen Fällen (siehe Template Method) einfach nicht erforderlich ist, eine Funktionalität bereitzustellen; in diesem Fällen sollte man dann aber eben leere Default-Implementierungen bereitstellen.

    Damit sollte man dann ganz gut über die Runden kommen. Worauf ich hinaus wollte, war eigentlich nur, dass es sehr selten sinnvoll ist, einerseits Default-Implementierungen anzubieten, diese aber andererseits überschreibbar zu machen. Die meisten solcher Fälle lassen sich mit Schablonenmethoden sauberer ansteuern.



  • Rein virtuelle Funktionen sind sowas von unnütz...
    oder ich zu blöd um den Sinn zu kapieren...

    Wenn ich in der Basisklasse rein virtuelle Funktionen hab (sprich kein
    Definition) wozu brauch ich die Vorherdeklaration, wenn ich die Methode
    doch in der Unterklasse sowieso deklarieren und definieren muss ?

    Wozu dient das?
    hat jemand ein Beispiel?



  • Quellcode schrieb:

    Rein virtuelle Funktionen ...
    Wozu dient das?
    hat jemand ein Beispiel?

    Programmierer X stellt ein Konzept zur Verfügung:

    struct A {
       virtual void machs() const = 0;
    };
    
    void machsMitallenA(vector<A*> const& v) {
       for(vector<A*>::const_iterator it=v.begin(); it != v.end(); ++it) {
          it->machs();
       }
    }
    

    Programmierer ABC nutzt dieses Konzept:

    struct B : A {
       void machs() const { cout << "mache B-Zeug\n"; }
    };
    
    struct C : A {
       void machs() const { cout << "mache C-Zeug\n"; }
    };
    
    int main() {
       vector<A*> v;
       v.push_back(new B);
       v.push_back(new B);
       v.push_back(new C);
       v.push_back(new A); // krach !!!
    
       machsMitallenA(v);
    
       return;
    }
    

    Die rein virtuelle Funktion A::machs() gibt also allen Nutzern von "Nachkommen von A" die Zusage, dass alle diese Gemeinsamkeit haben, "machs()" zu können.

    Gleichzeitig verpflichtet es jeden, der von A ableiten (und instantiieren) will, machs() auch umzusetzen.

    Es ist also eine Schnittstellenzusage und damit genauso hilfreich wie das Verwenden von Typen (statt void*) und const bei Funktionsdeklarationen.

    Gruß,

    Simon2.



  • Konrad Rudolph schrieb:

    Damit sollte man dann ganz gut über die Runden kommen. Worauf ich hinaus wollte, war eigentlich nur, dass es sehr selten sinnvoll ist, einerseits Default-Implementierungen anzubieten, diese aber andererseits überschreibbar zu machen. Die meisten solcher Fälle lassen sich mit Schablonenmethoden sauberer ansteuern.

    Einer von uns beiden hat Template Methode nicht verstanden...

    Template Method in meinen Augen ist eine Möglichkeit Teile einer Funktion oder eines Algorithmus redefinieren zulassen. In meinen Augen kommt da nirgendwo pure virtual vor - es ist nichts anderes als ein praktisches interface über eine virtuelle Schnittstelle gepackt -> daher ja auch Sutters NVI Idiom. In C++ Coding Standards kommt in dem NVI Abschnitt zB auch nie das wort pure virtual vor...



  • Quellcode schrieb:

    Rein virtuelle Funktionen sind sowas von unnütz...
    oder ich zu blöd um den Sinn zu kapieren...

    Wenn ich in der Basisklasse rein virtuelle Funktionen hab (sprich kein
    Definition) wozu brauch ich die Vorherdeklaration, wenn ich die Methode
    doch in der Unterklasse sowieso deklarieren und definieren muss ?

    Wozu dient das?

    Ganz einfach dazu, dass die Basisklasse die abgeleitete Klasse zwingt, eine Definition bereitzustellen. Das ist doch sehr sinnvoll: Die Basisklasse bestimmt, *was* zu tun ist, die abgeleitete Klasse bestimmt, *wie* es getan wird.

    hat jemand ein Beispiel?

    struct ReadStream {
        virtual void open(string const& resource) = 0;
        virtual string read_all() = 0;
    };
    
    struct FileReadStream : ReadStream {
        void open(string const& resource) { ifs.open(resource.c_str()); }
        string read_all() {
            return string(istreambuf_iterator<char>(ifs), istreambuf_iterator<char>());
        }
    
    private:
        fstream ifs;
    };
    
    struct HttpReadStream : ReadStream {
        void open(string const& resource) { hres = OpenHttpResource(resource.c_str()); }
        string read_all() {
            stringstream ret; char buffer[80];
            int read;
            while ((read = HttpReadStream(hres, buffer, 80) != 0) {
                if (read !=80) buffer[read] = '\0';
                ret << buffer;
            }
            return ret.str();
        }
    
    private:
            HANDLE hres;
    };
    
    string load_whole_file(ReadStream& stream, string const& resource) {
        stream.open(resource);
        return stream.read_all();
    }
    
    int main() {
        ReadStream* s;
        string path;
    
        cout << "Enter path name: " << flush;
        cin >> path;
        if (path.find("http://") == 0)
            s = new HttpReadStream();
        else
            s = new FileReadStream();
        string result = load_whole_file(s, path);
    }
    


  • Danke für die Antworten, jetzt bin ich schlauer 🙂



  • man seid ihr theoretisch. Ob pure virtual, virtual oder nicht ergibt sich doch aus der programmlogik.



  • powhu schrieb:

    man seid ihr theoretisch. Ob pure virtual, virtual oder nicht ergibt sich doch aus der programmlogik.

    und woraus ergibt sich die Programmlogik ?

    Gruß,

    Simon2.



  • Simon2 schrieb:

    powhu schrieb:

    man seid ihr theoretisch. Ob pure virtual, virtual oder nicht ergibt sich doch aus der programmlogik.

    und woraus ergibt sich die Programmlogik ?

    Programmlogik ist vielleicht der falsche Begriff. Ich hab noch nie nach irgendwelchen Regeln Methodenattribute vergeben. Wenn ich ein Interface brauche, dann mach ich halt alles pure virtual. Brauch ich ne abstrakte Klasse, dann nur nen Teil pure virtual. Und wenn man Methoden überschreiben können soll, dann halt virtuel. Das ergibt sich halt wie die Parameter und Rückgabewerte einer Methode, da schau ich nicht ob das "Template Method" oder sonst was ist.


Log in to reply