Überschreiben nichtvirtueller Elementfunktionen



  • Aus einer Diskussion zwischen elise und mir ist folgendes Problem entstanden:

    struct A {
      void foo() { ... }
    };
    
    struct B : A {
      void foo() { ... }
    };
    

    Vom objektorientierten Standpunkt her würde ich sagen, dass das sinnlos ist, da die "falsche"* Methode aufgerufen wird, wenn man ein B als A& anspricht, und gleich als Stil-Regel formulieren: Elementfunktionen, die überschrieben werden sollen, müssen virtuell sein. Soweit ich weiß, läßt die Mehrzahl der OO-Sprachen solche Konstrukte auch nicht zu (z.B. Java-Methoden sind immer virtual (vor final abgesehen), Eiffel-Operationen immer dann, wenn sie überschrieben werden))

    Aber in C++ ist nicht-virtuell nunmal Standard. Ist das mehr aus historischen Gründen (C with classes hatte kein virtual), oder um die Implementation der Sprache zu vereinfachen, oder gibt es tatsächlich sinnvolle Programmierstile, die das nutzen?

    😉 "falsch" im Sinne von Bertrand Meyer: A::foo erfüllt nur die Klasseninvariante von A, so dass die möglicherweise restriktivere Klasseninvariante von B nach fälschlichem Aufruf von A::foo auf ein tatsächliches B-Objekt verletzt sein kann.



  • wenn man weiss, dass man das ganze nicht in einem polymorphen umfeld verwenden will, kann das laufzeit sparen.



  • oder gibt es tatsächlich sinnvolle Programmierstile, die das nutzen?

    Was nutzen? Im sinne von Template Method ist es z.B. eine feine Sache. Also das man *nicht* überschreibt.

    Ansonsten gibt's in C++ ja noch das zero-cost-principle.

    [ Dieser Beitrag wurde am 28.05.2003 um 08:04 Uhr von HumeSikkins editiert. ]



  • Original erstellt von HumeSikkins:
    **
    Was nutzen?
    **

    Klassen public zu vererben und dabei nicht-virtuelle Methoden zu überschreiben.



  • Hallo,
    der Grund dafür, dass es nicht-virtuelle Methoden gibt leuchtet mir ein. Scott Meyers schreibt in "Effective C++"- Item 36:

    The purpose of declaring a nonvirtual function is to have derived classes inherit a function interface as well as a mandatory implementation.

    Zwar sagt der selbe Scott Meyers in Item 37 (Never redefine an inherited nonvirtual function), dass man nicht-virtuelle Methoden nicht überschreiben soll, allerdings ist dieses "never" in meinen Augen zu streng.
    Wichtig ist, dass man die "invariant over specialization" nicht verletzt. Das heißt solange das für den Client sichtbare Verhalten gleich bleibt (den Vertrag erfüllt), sehe ich keinen weiteren Grund der gegen das Überschreiben spricht.

    Ich habe das zwar selber noch nie gebraucht, ich könnte mir aber z.B. vorstellen, dass man eine nicht-virtuelle Funktion aus Performance-Gründen überschreibt.
    Bsp:

    class Base
    {
    public:
    void foo() 
    {
    // ...
        do_foo();
    }
    protected:
    virtual void do_foo() = 0;
    
    };
    
    class Derived : public Base
    {
    public:
    void evaluate() 
    {
        // ...
        // Aufruf statisch Binden -> inline möglich
        Derived::do_foo();
    }
    protected:
    inline virtual void do_foo()
    {
    // ...
    }
    
    };
    

    In Teilsystemem die nur mit der Basisklasse arbeiten, ist do_evaluate() immer ein virtueller Aufruf.
    Teilsysteme die nicht mit Basisklassenrefrenzen arbeiten (also von den konkreten Klassen abhängigen) bekommen dafür statische Bindung.

    Wichtig ist natürlich, dass das Verhalten nach außen *identisch* ist. Egal ob ich Base::foo oder Derived::foo aufrufe.

    Ob das irgendjemand schon mal tatsächlich gebraucht hat oder ob das sinnvoll ist, kann ich aber nicht beurteilen.

    [ Dieser Beitrag wurde am 28.05.2003 um 12:15 Uhr von HumeSikkins editiert. ]



  • danke für die antworten..

    🙂



  • Original erstellt von Bashar:
    Aus einer Diskussion zwischen elise und mir ist folgendes Problem entstanden:

    ehekrach?



  • wieso sollte eine diskussion ein krach sein?

    übrigens: trollen bitte im ot 😉



  • Heinz->accept(elise->getLastStatement());
    


  • @Hume:
    Ich werde aus deinem Beispiel nicht schlau. Die einzige Methode, die du überschreibst, ist bereits in der Basisklasse virtuell, also was hat das mit dem Thema zu tun? Oder sollte vielleicht Derived::evaluate() eigentlich foo() heißen *g*?

    Wie dem auch sei, ich finde nicht, daß diese Forderung von Meyers ("Never redefine an inherited nonvirtual function") zu streng ist. Sondern im Gegenteil ziemlich wichtig. Weil man nämlich ansonsten deine Voraussetzung unterstellen muß:

    Wichtig ist natürlich, dass das Verhalten nach außen *identisch* ist. Egal ob ich Base::foo oder Derived::foo aufrufe.

    Und das ist eine äußerst wackelige Angelegenheit!
    Nur mal angenommen, Base::foo() wird später geändert, so daß sich das Verhalten ändert (vielleicht wird ein Fehler gefixt oder sowas). Als Programmierer von Base hätte ich damit keine Probleme, eben *weil* foo() nicht virtuell ist. Und plötzlich sehen alle Derived-Klassen äußerst alt aus.....

    In meinen Augen ist Kapselung auch eine Sache zwischen Basis- und Unterklassen. Also sollten Unterklassen *ausschließlich* die Schnittstelle ihrer Basisklassen betrachten. Und das fehlende virtual vor einer Methode bedeutet hier: Nicht überschreibbar, never ever! Da zwingt mir die Basisklasse einen Teil ihrer Schnittstelle auf. Und ich gehe einfach davon aus, daß das so gewollt und sinnvoll ist.

    BTW: Eigentlich müßte der Standard das Überschreiben nichtvirtueller Methoden verbieten - finde ich.

    Stefan.



  • Oder sollte vielleicht Derived::evaluate() eigentlich foo() heißen *g*?

    Exakt. Ich hatte das Beispiel zuerst mit evalute und do_evaluate. Dann war mir das zu konkret und beim Korrigieren habe ich ein evalute vergessen 🙂

    Wie dem auch sei, ich finde nicht, daß diese Forderung von Meyers ("Never redefine an inherited nonvirtual function") zu streng ist. Sondern im Gegenteil ziemlich wichtig

    Wichtig finde ich persönlich schon wieder zu schwach. Umgangssprachlich finde ich das "never" sogar am passendsten. Nur sollte aus dem "never" halt kein "verboten durch den Standard" werden. Das wäre mir zu streng.

    In meinen Projekt-Style-Guides würde ich diese Regel übrigens immer aufnehmen. Das ändert aber nichts an der Tatsache, das ich mir Situationen vorstellen kann, wo es sinnvoll sein kann diese Regel zu brechen. Und in diesem Fall scheint mir das Brechen der Regel besser als blinder Gehorsam.



  • @Hume:

    Und in diesem Fall scheint mir das Brechen der Regel besser als blinder Gehorsam.

    Scheint das nicht immer so *g*?
    Trotzdem sehe ich das anders. Gerade wenn in deinen (wie natürlich auch in meinen) Projekt-Richtlinien die Regel besteht, daß nichtvirtuelle Methoden nicht überschrieben werden sollten/dürfen, ist es am riskantesten, die Regel zu brechen. Wie etwa in meinem obigen Beispiel.

    Und, ehrlich gesagt, abgesehen von eher akademischen Fällen, die eine derartige Vorgehensweise angebracht erscheinen lassen, kann ich mir nicht vorstellen, unter welchen Umständen es wirklich sinnvoll sein kann, eine nichtvirtuelle Methode zu überschreiben. Wie sähe denn ein Beispiel aus dem Leben aus?

    Stefan.



  • Wie sähe denn ein Beispiel aus dem Leben aus?

    Für Beispiele aus dem Leben bin ich der falsche Ansprechpartner. Ich programmiere weder professionell nocht privat und einige Menschen werfen mir sogar vor, dass ich nicht lebe.

    Ich denke aber eher, dass es eben gerade nicht in "akademischen Fällen" notwendig ist. Aus akademischer Sicht würde ich das Überschreiben verbieten. In Produktionscode wo akademische Zwänge eher eine untergeordnete Rolle spielen, erscheint mir die Flexibilität von C++ viel eher nötig zu sein.

    Hier habe ich gerade gelesen, dass die C++ Standardbibliothek von dieser Möglichkeit gebrauch macht (u.A. rdbuf) . Das heißt zwar nicht, dass das Redefinieren einer nicht-virtuellen Methode dadurch automatisch gut ist, es zeigt aber, dass es auch nicht immer so tragisch ist.

    Robert C. Martin sagt hier zum Thema:
    "Never redefine an inherited nonvirtual function."

    "Never" is a strong word. There is a whole style of programming (Data
    Abstraction) which is based upon the ability to override non virtual
    functions. These can work very nicely where polymorphism is based
    upon generics (templates) rather than on virtual functions.



  • *huestel*.... ok, mal eine ganz dumme, wahrscheinlich auch noobige Frage:

    wieso sollte man solche Elemente immer virutal machen? Ich habe z. B. in meinem aktuellen Projekt eine Klasse ColorSprite, mit der nicht-virtuellen Methode draw(). Viele andere Klassen erben von ColorSprite, und manche davon ueberschreiben draw(), das kann dann z. B. so aussehen:

    void foo::draw()
    {
      // ... irgendwas Klassenspezifisches
      ColorSprite::draw();
    }
    

    was ist daran denn falsch? 😕 bzw. was wuerde es mir bringen, ColorSprite::draw() virtual zu machen, wenn ich sicher bin, niemals Polymorphie zu brauchen?

    P.S. wie ich nach einem Beispiel gesucht hab ist mir aufgefallen, dass in der neuesten Version keien Child-Klass mehr draw() ueberschreibt, eigetnlich hat sich die Sache damit erledigt... 🙂

    Aber trotzdem: ist sowas immer schlechter Stil?



  • ist sowas immer schlechter Stil?

    Immer außer... 🙂
    In dem von dir beschriebenen Fall aber definitiv.
    Du schreibst zwar:

    wenn ich sicher bin, niemals Polymorphie zu brauchen?

    Die Situation die du beschreibst, schreit aber gerade zu nach Polymorphie. Alle Objekte können gezeichnet werden. Wie das genau geschieht unterscheidet sich aber. Ein klassischeres Beispiel für eine virtuelle Funktion wirst du kaum finden 🙂



  • @Hume:
    Hmmm, ich glaube, ich verstehe dich jetzt besser, bzw. die Unterschiede zwischen deiner und meiner Denkweise. Wahrscheinlich sind unsere Standpunkte einfach sehr verschieden. (Und was das angeht, ist mein Standpunkt wohl auch ein anderer als der der Designer der Standardbibliothek *g*)

    Ich befasse mich tatsächlich sehr stark mit der Pragmatik der Sprache. Mir geht es natürlich auch darum guten Code zu schreiben und eine technisch elegante Lösung spricht mich immer an. Aber das wichtigste scheint mir der "Kommunikationsaspekt" der Sprache zu sein: Was sage ich einem Leser mit meinem Code? (Das ist nicht immer ein anderer, vielleicht bin ich es selbst, wenn ich nächstes Jahr einen Fehler beseitigen muß.) Drücke ich mich klar aus, oder kann es Mißverständnisse geben? Usw.

    Und in diesem Sinne ist für mich die Möglichkeit (oder gar Praxis), nichtvirtuelle Methoden zu überschreiben eine Falle. Ich würde tatsächlich Programmierrichtlinien erlassen, in denen sowas verboten wird. Obwohl es (vielleicht) Fälle gibt, bei denen es sinnvoll wäre. Obwohl es mich vielleicht selbst ärgern würde, wenn mir so ein Fall in der Praxis unterkommt. Ich glaube einfach, es ist besser, das Zwielicht der Sprache zu meiden, selbst wenn man sich dadurch einschränken muß.

    Stefan.



  • P.S.:

    In Produktionscode wo akademische Zwänge eher eine untergeordnete Rolle spielen, erscheint mir die Flexibilität von C++ viel eher nötig zu sein.

    Das sehe ich genau anders herum! Gerade im Produktionscode ist die Flexibilität der Sprache oft von Übel, weil die Programmierer oft auf sehr verschiedenen Leistungsstufen stehen. Ein C++-Genie (das sein Genie auch zeigen will *g*) in einem ansonsten durchschnittlichen Team ist die Hölle! Spätestens ab Version 3.0.

    Stefan.



  • Vielleicht könnte man das überschreiben nicht virtueller Methoden dazu verwenden ein eigenes RTTI-System aufzubauen.


Anmelden zum Antworten