Modernes OOP unter C++



  • Simon2 schrieb:

    Konrad Rudolph schrieb:

    ...
    Das ist IMHO ein sehr gleitender Übergang.
    'size' muss eine Memberfunktion sein.
    'empty' auch? Nein, denn es kann über 'size' implementiert werden.
    Trotzdem besitzt jeder Container diese Funktion.
    ...

    - Stimmt
    - stimmt
    - stimmt
    - stimmt - finde ich aber schade.

    ich find's gut. So kann ich zum beispiel in konstanter Zeit überprüfen, ob eine Liste leer ist, wohingegen der size-Aufruf lineare Zeit kosten darf (und es imho bei einer brauchbaren Implementierung auch tun sollte).



  • Konrad Rudolph schrieb:

    Was ist „etwas viel“? Was spricht dagegen, wenn ein Container viele Konzepte implementiert?

    http://www.cplusplus.com/reference/algorithm/ << Das ist die Liste der std::algorythmen - und die verdient durchaus die Bezeichnung "etwas viel" Wenn jeder Container von einer entsprechenden Basisklasse erben wuerde, waere das ein wahrer Vererbungsdschungel. Stell dir mal vor du implementierst selbst einen Container, und parallel dazu einen Algorithmus mit dem du auf beliebigen Containern etwas machen moechtest, was in den Standardalgorithmen noch nicht drin ist. Fuer letzteren musst du alle bereits vorhandenen Containerdefinitionen die du nutzt, abaendern, damit sie von deinem neuen Interface erben, und der Container muss von allem erben was in Betracht kommt fuer eine spaetere Benutzung. (Und wenn er fuer was benutzt wird was du nicht in Betracht gezogen hast viel spass beim neucompilieren des dann geaenderten Containers.
    Da nimmt man doch lieber die Plug&Play-STL die man nach belieben erweitern kann, auch wenns fuer die Vertreter der "OOP-muss-Vererbung-sein!"-Lobby kein OOP ist.



  • pumuckl schrieb:

    Konrad Rudolph schrieb:

    Was ist „etwas viel“? Was spricht dagegen, wenn ein Container viele Konzepte implementiert?

    http://www.cplusplus.com/reference/algorithm/ << Das ist die Liste der std::algorythmen - und die verdient durchaus die Bezeichnung "etwas viel" Wenn jeder Container von einer entsprechenden Basisklasse erben wuerde, waere das ein wahrer Vererbungsdschungel.

    Auch nicht schlimmer als diese Liste. Es ist ja kein Unterschied, ob's freie oder gebundene Funktionen sind.

    Stell dir mal vor du implementierst selbst einen Container, und parallel dazu einen Algorithmus mit dem du auf beliebigen Containern etwas machen moechtest, was in den Standardalgorithmen noch nicht drin ist. Fuer letzteren musst du alle bereits vorhandenen Containerdefinitionen die du nutzt, abaendern, damit sie von deinem neuen Interface erben, und der Container muss von allem erben was in Betracht kommt fuer eine spaetere Benutzung.

    Nein, eben nicht. Sondern Du würdest (wieder in einer imaginären Sprache) folgendes schreiben:

    trait MeinTollerNeuerContainerTrait[T as Printable]
        function MeineTollemethode()
            # Alle Elemente ausgeben:
            for element in this
                Console.WriteLine(element)
        end
    end
    
    trait Range is MeinTollerNeuerContainerTrait
    

    – Man braucht quasi die Möglichkeit, „Mix-Ins“ zu definieren. Mehr nicht.

    Da nimmt man doch lieber die Plug&Play-STL die man nach belieben erweitern kann

    Nochmal: Was mir vorschwebt, ist semantisch dasselbe wie die STL-Algorithmen (wenn man vom Methoden-Lookup-Algorithmus absieht), nur die Syntax ist anders, um die Zugehörigkeit besser widerzuspiegeln.



  • Konrad Rudolph schrieb:

    Nochmal: Was mir vorschwebt, ist semantisch dasselbe wie die STL-Algorithmen (wenn man vom Methoden-Lookup-Algorithmus absieht), nur die Syntax ist anders, um die Zugehörigkeit besser widerzuspiegeln.

    Wobei das Thema "Modernes OOP unter C++" und nicht einer anderen Sprache ist. Der Fragesteller wollte wohl wissen warum die Designentscheidung so und nicht anders in C++ getroffen wurde, als zu wissen wie du es mit anderen Mitteln umsetzen würdest ;p

    cu André



  • asc schrieb:

    Wobei das Thema "Modernes OOP unter C++" und nicht einer anderen Sprache ist. Der Fragesteller wollte wohl wissen warum die Designentscheidung so und nicht anders in C++ getroffen wurde, als zu wissen wie du es mit anderen Mitteln umsetzen würdest ;p

    Stimmt, ich bin abgedriftet. Aber wie ich vor einigen Tagen schon in einem anderen Thread sagte: So ist das nunmal in Diskussionen. Ohne solche Diskussionen wäre das Forum doch wesentlich weniger wert.


  • Mod

    Konrad Rudolph schrieb:

    ...
    Das ist IMHO ein sehr gleitender Übergang.
    'size' muss eine Memberfunktion sein.
    'empty' auch? Nein, denn es kann über 'size' implementiert werden.
    Trotzdem besitzt jeder Container diese Funktion.
    ...

    - ja.
    - Warum? Der gleiche Fehlschluss stört mich auch bei Sutters ansonsten excellenter Analyse von basic_string. Was hat die Wahl des Interfaces mit der Implementierbarkeit der Funktionalität zu tun? Oder bei Sutter - warum sollte es den Anwender interessieren, welcher der verschiedenen Overloads nun der Allgemeinste ist (der kriegt die Membersyntax)
    - nicht unter den Komplexitätsbedingungen, die an empty gestellt werden (list), allerdings ist empty als begin()==end() implementierbar



  • camper schrieb:

    Konrad Rudolph schrieb:

    'size' muss eine Memberfunktion sein.

    - Warum? Der gleiche Fehlschluss stört mich auch bei Sutters ansonsten excellenter Analyse von basic_string. Was hat die Wahl des Interfaces mit der Implementierbarkeit der Funktionalität zu tun? Oder bei Sutter - warum sollte es den Anwender interessieren, welcher der verschiedenen Overloads nun der Allgemeinste ist (der kriegt die Membersyntax)

    So argumentiere ich ja gar nicht. Andererseits: Wie würdest Du es machen? 'size' als Algorithmus implementieren? Denkbar. Aber was wären die Vorteile?

    Mir ging es ja in meiner Argumentation darum, dass ich die Unterscheidung grundsätzlich für ziemlich willkürlich halte. Eine Schnittstelle ist für mich konzeptuell das, was man mit einer Klasse (bzw. Exemplaren davon) machen kann.



  • linu(x)bie schrieb:

    Wenn ein Container/Collection jedoch das Interface (z.B.) "Sortable" bereitstellt, dann ist das eine Eigenschaft!

    Eine Sequenz mit Objekten vom Typ T ist genau dann sortiertbar, wenn T eine Ordnungsrelation (hier operator<) besitzt. Daher ist die Eigenschaft sortierbar keine des Sequenzcontainers, sondern sie ist eine der Elemente.

    Ich weiß nun, dass dieser Container sortierbar ist. (Was man sich in der STL mühsam zusammenlesen muss).

    Das ist überflüssig, das weiß man schon vorher. Die Eigenschaft hängt nur von Deinen Elementen ab und nicht vom Container!

    Beispiel:

    #include <vector>
    #include <algorithm>
    
      class A {
        int i_;
      public:
        A(int const i) : i_(i) {}
      };
    
      class B {
        int i_;
      public:
        B(int const i) : i_(i) {}
        bool less (B const& rhs) const {
          return this->i_ < rhs.i_;
        }
      };
      bool operator< (B const& lhs, B const& rhs) {
        return lhs.less(rhs);
      }
    
    int main () {
      std::vector<A> a;
      std::vector<B> b;
    
      for (size_t i = 100; i != 0; --i) {
        a.push_back(A(i));
        b.push_back(B(i));
      }
    
      std::sort (b.begin(), b.end());
      // std::sort (a.begin(), a.end());
    }
    

    std::vector<B> ist sortierbar, std::vector<A> ist es nicht, es hängt also gar nicht vom Container ab, sondern von T. Wenn t.operator< oder operator<(T,T) existiert, dann ist auch jeder Sequenzcontainer sortierbar.


  • Mod

    Konrad Rudolph schrieb:

    camper schrieb:

    Konrad Rudolph schrieb:

    'size' muss eine Memberfunktion sein.

    - Warum? Der gleiche Fehlschluss stört mich auch bei Sutters ansonsten excellenter Analyse von basic_string. Was hat die Wahl des Interfaces mit der Implementierbarkeit der Funktionalität zu tun? Oder bei Sutter - warum sollte es den Anwender interessieren, welcher der verschiedenen Overloads nun der Allgemeinste ist (der kriegt die Membersyntax)

    So argumentiere ich ja gar nicht. Andererseits: Wie würdest Du es machen? 'size' als Algorithmus implementieren? Denkbar. Aber was wären die Vorteile?

    Mir ging es ja in meiner Argumentation darum, dass ich die Unterscheidung grundsätzlich für ziemlich willkürlich halte. Eine Schnittstelle ist für mich konzeptuell das, was man mit einer Klasse (bzw. Exemplaren davon) machen kann.

    Das war nicht gegen deine Argumentation gerichtet, im Gegenteil. Ich denke wir sind hier auf gleicher Wellenlänge. Es ist nicht nur für den Nutzer lediglich ein rein syntaktischer Unterschied (wenn wir jetzt mal vom Name-lookup absehen), sondern auch aus Sicht der Implementation. Wenn ein Interface nur eine Fassade um etwas herum ist, dann muss sie ohnehin alles delegieren.



  • ~john schrieb:

    linu(x)bie schrieb:

    Wenn ein Container/Collection jedoch das Interface (z.B.) "Sortable" bereitstellt, dann ist das eine Eigenschaft!

    Eine Sequenz mit Objekten vom Typ T ist genau dann sortiertbar, wenn T eine Ordnungsrelation (hier operator<) besitzt. Daher ist die Eigenschaft sortierbar keine des Sequenzcontainers, sondern sie ist eine der Elemente.

    Nicht jeder Container ist ein Sequenzcontainer. Versuch mal ein Hashtable zu sortieren 🙂



  • Man programmiert mit C++ genauso OO wie mit Java. Ob du OOPst ist eine Designfrage und hat nichts mit der Sprache zu tun. Eine static methode in einer Utility Klasse ist genauso wenig OO wie eine Funktion in einem Namespace. Schade ist halt, dass man in der STL alles in einen Namespace gestopft hat.



  • 1600x1200 schrieb:

    Man programmiert mit C++ genauso OO wie mit Java. Ob du OOPst ist eine Designfrage und hat nichts mit der Sprache zu tun. Eine static methode in einer Utility Klasse ist genauso wenig OO wie eine Funktion in einem Namespace. Schade ist halt, dass man in der STL alles in einen Namespace gestopft hat.

    Dem wiederspreche ich. Ja, im Kern ist OO Sprachunabhängig, aber je nach dem wie die Sprache ausgelegt ist, ist es manchmal nicht sinnvoll sich 100% auf klassisches OO zu beschränken (z.B. machen Operatoren außerhalb der Klasse in C++ sehr viel Sinn, Hilfsfunktionen habe ich auch lieber als notgedrungen alles in Klassen zu stopfen - wenn gleich ich ansonsten ein OO-Beführworter bin). Und C++ ist nunmal keine OO Sprache sondern wie schon angesprochen eine Multiparadigmensprache.

    Wenn ich C++ Programmiere ist das etwa 70% OO, der Rest baut auf generische Programmierung 20% ob mit oder ohne Klassen und ungebundenen Hilfsfunktionen 10% auf. Wobei ich immer mit Namensräumen arbeite so das man, entsprechende UML-Tools vorausgesetzt, diese in UML mit Hilfsklassen simulieren kann.

    Wenn ich andere Sprachen verwende (z.B. Java/C#) sieht das natürlich wieder anders aus.

    cu André



  • "Modernes OOP"? Ich glaube deine (linuxbie) Vorstellung von OOP entspricht eher dem 90er-OOP denken. Große Vererbungshierachien sind nichts schönes und flexibles. Deshalb benutzt man heutzutage lieber späte Typbindungen (nicht nur in C++. Schau dir zB Python oder Ruby an). Wenn man Eigenschaften über Vererbungshierachien ausdrückt, muss man einfach zu viele Dinge apriori wissen. Die Anforderungen ändern sich schnell und plötzlich ergeben sich andere Usecases etc. Sollte man jedes mal den Code umwerfen und neue Eigenschaften in die Vererbungshierachie einfügen? Sicher nicht. Noch schlimmer ist es, wenn man zwei unterschiedliche Bibliotheken kombinieren muss, wie Klassen mit der gleichen Eigenschaft haben und diese auch durch Vererbung ausdrücken. Aber beide Bibliotheken haben eigene Interfaces/Basisklassen für diese Eigenschaft, so dass man eben doch nicht interoperabel sein kann.

    Deshalb nimmt man lieber späte Typbindung. So wie es zB die STL macht.

    Freie Funktionen sind auch nichts böses. Siehe http://www.ddj.com/cpp/184401197

    @Konrad Rudolph
    Das was du vorschlägst heißt Concepts und wird Bestandteil von C++0x. (Implementierung gibt es zB schon als ConceptGCC).



  • rüdiger schrieb:

    @Konrad Rudolph
    Das was du vorschlägst heißt Concepts und wird Bestandteil von C++0x.

    Habe ich doch schon geschrieben. Aber das was ich meine, geht noch ein Stück weiter als Concepts.



  • vielleicht habe ich die richtige stelle in diesem thread nur noch nicht gefunden,

    aber genau das:

    Konrad Rudolph schrieb:

    Ein weiterer Vorteil dieser Schreibweise ist Vereinheitlichung; man kann dadurch erreichen, dass es nur noch einen Typ von Funktionsaufrufen gibt (wie auch immer die Syntax dafür aussieht). Dass man in den meisten Sprachen syntaktisch zwischen Methoden und freien Funktionen unterscheidet finde ich nicht prickelnd, weil dieser Unterschied semantisch nicht getragen wird.

    wird doch mit den Concepts realisierbar werden? also inwiefern gehst du noch ein stück weiter?



  • queer_boy schrieb:

    vielleicht habe ich die richtige stelle in diesem thread nur noch nicht gefunden,

    aber genau das:

    Konrad Rudolph schrieb:

    Ein weiterer Vorteil dieser Schreibweise ist Vereinheitlichung; man kann dadurch erreichen, dass es nur noch einen Typ von Funktionsaufrufen gibt (wie auch immer die Syntax dafür aussieht). Dass man in den meisten Sprachen syntaktisch zwischen Methoden und freien Funktionen unterscheidet finde ich nicht prickelnd, weil dieser Unterschied semantisch nicht getragen wird.

    wird doch mit den Concepts realisierbar werden? also inwiefern gehst du noch ein stück weiter?

    Hmm, seit wann kann ich mit Concepts schreiben 'mycoll.sort()'? Bei der Vereinheitlichung ging es mir darum, dass es nur eine Syntax für Methodenaufrufe gibt, nämlich 'Objekt.Methode()' versus 'Methode(Objekt)'.

    Aber diesen Unterschied meinte ich auch nicht. Es ging mir um die Lokalität der Definition, also das wegfallende Koenig-Lookup. Bei meinem Ansatz gibt es genau einen Ort, an dem eine Methode definiert sein kann, nämlich in der Klasse des assoziierten Objekts (bzw. in dessen Vererbungshierarchie).



  • Konrad Rudolph schrieb:

    Bei der Vereinheitlichung ging es mir darum, dass es nur eine Syntax für Methodenaufrufe gibt, nämlich 'Objekt.Methode()' versus 'Methode(Objekt)'.

    mit concepts wird wohl letzere möglichkeit favorisiert. (ob sich das durchsetzt, diese syntax dann in template-funktionen zu verwenden?)

    Aber diesen Unterschied meinte ich auch nicht. Es ging mir um die Lokalität der Definition, also das wegfallende Koenig-Lookup. Bei meinem Ansatz gibt es genau einen Ort, an dem eine Methode definiert sein kann, nämlich in der Klasse des assoziierten Objekts (bzw. in dessen Vererbungshierarchie).

    ok, alles klar, denke ich, das bedingt also einfach ein breiteres verständnis von klasse.



  • asc schrieb:

    1600x1200 schrieb:

    Man programmiert mit C++ genauso OO wie mit Java. Ob du OOPst ist eine Designfrage und hat nichts mit der Sprache zu tun. Eine static methode in einer Utility Klasse ist genauso wenig OO wie eine Funktion in einem Namespace. Schade ist halt, dass man in der STL alles in einen Namespace gestopft hat.

    Dem wiederspreche ich. Ja, im Kern ist OO Sprachunabhängig, aber je nach dem wie die Sprache ausgelegt ist, ist es manchmal nicht sinnvoll sich 100% auf klassisches OO zu beschränken (z.B. machen Operatoren außerhalb der Klasse in C++ sehr viel Sinn, Hilfsfunktionen habe ich auch lieber als notgedrungen alles in Klassen zu stopfen - wenn gleich ich ansonsten ein OO-Beführworter bin). Und C++ ist nunmal keine OO Sprache sondern wie schon angesprochen eine Multiparadigmensprache.

    Wenn ich C++ Programmiere ist das etwa 70% OO, der Rest baut auf generische Programmierung 20% ob mit oder ohne Klassen und ungebundenen Hilfsfunktionen 10% auf. Wobei ich immer mit Namensräumen arbeite so das man, entsprechende UML-Tools vorausgesetzt, diese in UML mit Hilfsklassen simulieren kann.

    Wenn ich andere Sprachen verwende (z.B. Java/C#) sieht das natürlich wieder anders aus.

    Inwiefern sollte generische Programmierung aus einem OO Design ein nicht OO Design machen?
    Und wie gesagt, ungebundene Hilfsfunktionen als static Methoden in ne Klasse zu stecken macht noch kein OOP.



  • 1600x1200 schrieb:

    Inwiefern sollte generische Programmierung aus einem OO Design ein nicht OO Design machen?

    Kommt darauf an ob du statische Beziehungen die ohne Vererbung funktionieren als OO bezeichnen möchtest.

    Ganz simples Beispiel

    template<typename T>
    void foo(const T& t)
    {
        t->foo();
    }
    

    Hier wird zwar verlangt das der Typ T eine konstante Methode foo besitzt, aber es existiert weder eine Bindung durch Vererbung/Interfaces noch durch eine solch fixe Bindung an die Signatur wie bei Vererbung. In diesem Fall hier darf die Methode einen beliebigen Rückgabewert haben, und muss sich mit null Parameter aufrufen lassen.

    Man kann es natürlich als eine Form der statischen Vererbung durchgehen lassen.

    1600x1200 schrieb:

    Und wie gesagt, ungebundene Hilfsfunktionen als static Methoden in ne Klasse zu stecken macht noch kein OOP.

    Dem ist mir bekannt, aber Hilfsfunktionen jeglicher Art - ob nun in Klassen oder außerhalb von Klassen - wiedersprechen eigentlich der OO-Lehre, oder lassen sich zumindestens nur dann durch UML abbilden wenn man sie in Klassen unterbringt. Und ich verwende nunmal irgendwie geartete Hilfsfunktionen wenn diese sinnvoller als ein Klassenkonstrukt sind (Dennoch ist mein Design mit Sicherheit näher an OO als bei einigen anderen Entwicklern die von sich behaupten OO zu programmieren aber eigentlich ausschließlich prozedurale Programmierung in Klassen stopfen - Aber ich nutze die Mittel halt so aus, wie sie in der Entsprechenden Stelle sinnvoll sind, am liebsten mittels OO Konzepten, aber halt nicht ausschließlich).

    cu André



  • Eigentlich ist das Template nur ein Implementierungsdetail. Ob das ganze dann OO ist hängt vom Kontext und vom verwendeten Typ T ab. Templates sind ja eigentlich nur erweitere Makros (Textersatz). Wenn eine andere Sprache diesen Komfort nicht bietet, musst du halt mehr tippen. Ob man ein Template verwendet oder nicht, ändert nichts an der Programmlogik und am eigentlichen Design.

    Ich bin aber auch nicht der Meinung, dass immer alles 100% OO sein muss.


Anmelden zum Antworten