Design Frage -> Member- oder nicht Memberfunktion



  • Shade Of Mine schrieb:

    non member bringen dir hier einen enormen vorteil wenn du mal mehr klassen hast: calc_top_right() kann fuer alle Shapes funktionieren die ein get_width() anbieten - ohne dass du neuen code schreiben musst - einfach nur calc_top_right als template funktion.

    Für welches Shape würde calc_bottom_right noch funktionieren und wirklich den bottom_right Punkt des Shapes und nicht der Boundingbox liefern?



  • Nein, alles was geht raus aus der Klasse.
    Siehe zB monoliths unstrung von Sutter, zB online: http://www.gotw.ca/gotw/084.htm oder in Effective C++ Style.
    Oder auch Meyers: http://www.ddj.com/cpp/184401197

    Alles was ich ausserhalb der Klasse habe, kann ich wiederverwenden. Generell ist eine foo.bar() Syntax furchtbar restriktiv.

    Wenn nun calc_top_right() non member ist, dann habe ich es automatisch fuer jedes Shape dass ein get_width() anbietet. ohne aufwand, einfach durch statische polymorphie.

    Die gleiche funktionalitaet koennte man erreichen indem man alles Shapes von einer abstrakten Klasse Shape ableitet und dort calc_top_right() implementiert.

    Aber ploetzlich sind wir von Shape abhaengig. Wenn ich jetzt ein calc_middle() will um den mittelpunkt zu bestimmen und der Designer von Shape hat daran nicht gedacht, dann pech gehabt.

    Hier kommen eben non member ins Spiel. Ich kann jederzeit eine calc_middle() funktion implementieren die fuer alle Shapes funktioniert. Und Shapes sind alles was ein get_width() und get_height() anbietet. Ich brauche kleine laufzeit polymorphie.

    Wir haben damit eine Moeglichkeit bestehende Klassen ohne vererbung zu erweitern. denn vererbung nur um funktionalitaet hinzuzufuegen ist boese.

    Wenn wir das in der STL zb gemacht haetten, dann koennte ich
    sort(container);
    sagen um jeden beliebigen container zu sortieren.

    Was dazu fuehrt, dass wir ploetzlich generischen Code viel einfacher schreiben koennen, weil man viel weniger spezialisieren muss.



  • Ich verstehe langsam, was du meinst und es macht auch alles Sinn.

    Aber dieses

    ohne aufwand, einfach durch statische polymorphie

    versteh ich nicht.
    Wie soll die Funktion auf einmal andere Formen aufnehmen, statt nur dem Rechteck.
    Dafür muss man sie doch von einer Basisklasse ableiten oder?

    Die gleiche funktionalitaet koennte man erreichen indem man alles Shapes von einer abstrakten Klasse Shape ableitet und dort calc_top_right() implementiert.

    Und da dachte ich, das wäre das Standardkonzept, wie es in jedem Buch steht. So kann man sich irren. Aber man lernt ja nie aus.

    \edit: ja durch Templates. Ist mir noch vorm Einschlafen eingefallen.


  • Administrator

    Chuck schrieb:

    ohne aufwand, einfach durch statische polymorphie

    versteh ich nicht.
    Wie soll die Funktion auf einmal andere Formen aufnehmen, statt nur dem Rechteck.
    Dafür muss man sie doch von einer Basisklasse ableiten oder?

    Durch Templates:

    template<typename ValueT>
    Coord calc_top_right(ValueT const& value);
    

    Grüssli



  • Shade Of Mine schrieb:

    Nein, alles was geht raus aus der Klasse.
    Siehe zB monoliths unstrung von Sutter, zB online: http://www.gotw.ca/gotw/084.htm oder in Effective C++ Style.
    Oder auch Meyers: http://www.ddj.com/cpp/184401197

    Grundsätzlich ist es mit klar, wieso man freie Funktionen Memberfunktionen vorziehen sollte.
    Nun habe ich mir mal Deinen ersten Link angesehen, und dabei sind mir dann aber Zweifel gekommen.
    Herb Sutter zieht da ja jede Menge Funktionen aus basic_string heraus und spricht an, dass die meisten der Funktionen sich auch auf andere STL-Container anwenden lassen würden.
    Wenn ich mir aber jetzt sowas wie seine empty()-Templatefunktion ansehe:

    template<class charT, class traits, class Allocator>
    bool empty( const basic_string<charT, traits, Allocator>& s )
    {
      return s.size() == 0;
    }
    

    dann ist dieses in der Form doch wieder nur für basic_string zu benutzen, oder?
    Für die verschiedenen anderen Container müsste ich dann wieder andere Versionen anbieten. Wo ist also der Vorteil?

    Ich würde das (vermutlich naiv) eher so machen:

    template<typename T>
    bool empty(const T& c)
    {
        return c.size() == 0;
    }
    


  • Grundsätzlich ist es mit klar, wieso man freie Funktionen Memberfunktionen vorziehen sollte.

    Oftmals macht auch beides sinn ...
    eine freie <template> Version die mit aehnlichen klassen auch funktioniert, und eine gebundene interne, die auf grund internas effizienter implementiert werden kann.

    Ansonsten wuerd ich mich Shade Of Mine anschliessen. Vor allen den "interfaces" nen einheitliches Gesicht verpassen. Das dankt dir spaeter der, der den code warten muss. und schoen in mindestens nen namespace kapseln ...

    Wenn man erst mal ne generische funktion hat, kann man spaeter bei der optimierung immer noch gebundene interne versionen bauen und auf die umsteigen, wenn das moeglich/ notwendig ist.

    Ciao ...



  • poasting in a design discussion thread.

    lohnt es sich noch bier kalt zustellen und knabberkram zu kaufen? 😃


  • Administrator

    sothis_ schrieb:

    lohnt es sich noch bier kalt zustellen und knabberkram zu kaufen? 😃

    1. Das ist grundsätzlich der erste Trollpost.
    2. Nein, gibt keinen Grund, kannst also ruhig wieder gehen.

    @Rest,
    Bis jetzt eigentlich keine Antworten gegen dieses Vorgehen. Meine Zweifel haben sich verflüchtigt 🙂
    Um die Klasse Rect gibt es übrigens einen namespace und somit auch um die Funktionen. Ich sollte vielleicht mal die Dokumentation umbennen von Global zu Free functions oder sowas ähnliches.

    Jetzt habe ich womöglich noch ein anderes Designproblem. Aber dazu mache ich einen neuen Thread auf, sobald ich mich nochmals selber damit gründlich beschäftigt habe.

    Danke!

    Grüssli



  • Trotzdem würde ich gerne meine Frage beantwortet haben. Auch wenn sie sich eher direkt auf die Ausführungen von Herb Sutter aus Shades Link bezieht.

    Mir ist irgendwie nicht klar, wo der Vorteil ist, wenn die Funktionen frei sind, wenn sich eh nur basic_string mit ihnen benutzen lässt. Sind die Beispiele evtl. nur schlecht gewählt?

    Wenn ich für alle Container die Funktionen wieder explizit überladen muss, kann ich sie doch eigentlich auch gleich wieder in die Klasse packen, oder?
    Mir würde sich die Frage nicht stellen, wenn er (Sutter) generellere Funktionen aufgeführt hätte, wie ich sie oben im Beispiel genannte habe.



  • Tachyon schrieb:

    Trotzdem würde ich gerne meine Frage beantwortet haben. Auch wenn sie sich eher direkt auf die Ausführungen von Herb Sutter aus Shades Link bezieht.

    Sutter geht in dem Link eben auf string ein.
    Wenn man das ganze natuerlich fuer alle Container will, dann ist ein

    template<typename ContainerT>
    bool empty(ContainerT const& cont) {
      return cont.size()==0;
    }
    

    notwendig.

    PS:
    @sothis_:
    Du koenntest hier ruhig mal etwas lernen und weniger trollposts machen. Wegen Leuten wie dir frage ich mich manchmal, warum ich das hier ueberhaupt mache. Kotzt mich an, echt.



  • Shade Of Mine schrieb:

    Sutter geht in dem Link eben auf string ein.
    Wenn man das ganze natuerlich fuer alle Container will, dann ist ein...

    Wo ist denn der Sinn, freie Funktionen für etwas zu definieren, was man ohnehin nur für einen Spezialfall benutzen kann? Ist das Beispiel nur schlecht gewählt? Mir ist das irgendwie nicht so recht klar.



  • SO weit ich das verstanden habe, geht es darum, die Kapselung soweit wie
    möglich zu erhalten.

    Und wenn ich mit 3 Methoden alles realisieren könnte, was mit dieser
    Klasse machbar sein muss, dann können alle anderen freie Funktion werden, da
    diese nur die 3 Methoden benötigen.

    Laut Sutter gibts keine Performance-Einschränkungen und wenn mal was erweitert
    werden müsste ist die ein leichteres, da man keine großartige
    Klassenhierarchie verwenden muss.



  • Shade Of Mine schrieb:

    @sothis_:
    Du koenntest hier ruhig mal etwas lernen und weniger trollposts machen. Wegen Leuten wie dir frage ich mich manchmal, warum ich das hier ueberhaupt mache. Kotzt mich an, echt.

    jetzt überreagiere doch nicht so. ist ja eine sehr angespannte stimmmung hier, dann geh ich halt wieder 🙂



  • Chuck schrieb:

    SO weit ich das verstanden habe, geht es darum, die Kapselung soweit wie
    möglich zu erhalten.

    Und wenn ich mit 3 Methoden alles realisieren könnte, was mit dieser
    Klasse machbar sein muss, dann können alle anderen freie Funktion werden, da[...]

    Ja, das ist soweit schon klar, ich hänge mich allerdings (vielleicht zu sehr) an seinem Beispiel auf.
    Das Interface der freien Funktionen ist ja nun direkt auf basic_string zugeschnitten. Da lässt sich (zumindest aus meiner sicht) nicht mehr allzu viel erweitern. Aber vielleicht verstehe ich auch nur was nicht richtig.



  • was sich mir als Frage zum zweiten Text stellt:
    Was haben friend Funktionen für einen Vorteil gegenüber Membern?(Ich ziele da auf den Algorithmus zu Beginn des Textes ab)
    Ich habe das Argument mit der Kapselung verstanden und auch dass das Interface dadurch teilbarer wird, aber ist eine Friendfunktion nicht auch in einem gewissen Maße irreführend?


  • Administrator

    Tachyon schrieb:

    Shade Of Mine schrieb:

    Sutter geht in dem Link eben auf string ein.
    Wenn man das ganze natuerlich fuer alle Container will, dann ist ein...

    Wo ist denn der Sinn, freie Funktionen für etwas zu definieren, was man ohnehin nur für einen Spezialfall benutzen kann? Ist das Beispiel nur schlecht gewählt? Mir ist das irgendwie nicht so recht klar.

    Sutter hat sich dort nur gerade auf die Strings konzentriert, da er schaute, welche Funktionen man rausnehmen kann. Und wahrscheinlich um den Leser nicht zu sehr zu verwirren, hat er es derzeit bei dem basic_string gelassen.

    Also ist das Beispiel vielleicht etwas schlecht gewählt, zufrieden? Wie kann man so ein Drama, um so etwas simples machen. Du solltest den Beruf Unternehmensberatung anstreben :p

    @JustAnotherNoob,
    Wenn ich dich richtig verstehe, fragst du, wieso eine Friendfunktion einer Membermethode bevorzugt werden sollte?
    Ich zitiere mal direkt Sutter, aus dem ersten Text:

    (There are some rare exceptions such as operations needing conversions on their left-hand arguments and some like operator<<() whose signatures don't allow the *this reference to be their first parameters; even these can normally be nonfriends implemented in terms of (possibly virtual) members, but sometimes doing that is merely an exercise in contortionism and they're best and naturally expressed as friends.)

    Oder dann habe ich nicht verstanden was du meinst.

    Grüssli



  • Und was ist mit Information hiding? Warum weiß eine globale Funktion wie man an die Eckpunkte eines Rechtecks kommt? Sowas weiß das Rechteck und sonst keiner. Was würde den passieren, wenn man das Rechteck auch drehen können will und es dann nicht mehr parallel zu den Achsen liegt? Dann würde die globale Funktion was falsches berechnen und müsste geändert werden. Das hätte zu Folge das sie für andere Shapes nicht mehr funktioniert. Welche anderen Shapes diese Funktionen überhaupt noch nutzen könnten hat sowieso noch keiner gesagt.



  • campers meing dazu würde mich interessieren.



  • Und was wäre mit globalen Funktionen (z.B. als Template), die auf die jeweiligen Methoden zugreifen? Dann könnte alles intern geregelt werden und eine gemeinsame Schnittstelle wäre dennoch vorhanden...



  • hmmm??? schrieb:

    Und was ist mit Information hiding? Warum weiß eine globale Funktion wie man an die Eckpunkte eines Rechtecks kommt? Sowas weiß das Rechteck und sonst keiner. Was würde den passieren, wenn man das Rechteck auch drehen können will und es dann nicht mehr parallel zu den Achsen liegt? Dann würde die globale Funktion was falsches berechnen und müsste geändert werden. Das hätte zu Folge das sie für andere Shapes nicht mehr funktioniert. Welche anderen Shapes diese Funktionen überhaupt noch nutzen könnten hat sowieso noch keiner gesagt.

    Ja, die Eckpunkte sind schlecht gewaehlt als Beispiel.
    Aber das ist immer so, es geht um ein Prinzip aber anstatt nachzudenken wird sich an Beispielen aufgehaengt.

    Sehen wir uns dagegen std::string an, dann ist es einfach 20-30 Beispiele zu bringen was als non member deutlich besser waere. Hier bei dem Rechteck Beispiel geht es deshalb nicht gut, weil es ein minimalistisches Beispiel ist dass hier im Thread nichtmal fertig implementiert ist.

    Ist das echt so schwer zu verstehen? Muss man alles klein bis ins hinterletzte Details durchdesignen bevor ihr das Konzept versteht?

    OK. Machen wir das halt:

    nehmen wir std::string und die member funktion assign().
    Kann ich perfekt als

    template<typename ContainerT, typename Iter>
    void assign(ContainerT& cont, Iter first, Iter last) {
      cont.clear();
      while(first!=last) {
        cont.push_back(*first);
        ++first;
      }
    }
    

    implementieren.

    Der Vorteil:
    ich habe jetzt automatisch ein range insert fuer _alles_ dass ein push_back und ein clear anbietet.

    Man kann die komplette Schnittstelle aller container dadurch enorm verbessern. Die STL hat den ersten Schritt gemacht und iteratoren als Konzept genommen um algorithmen von datenstrukturen zu abstrahieren. aber viele algorithmen sind in den containern drinnen. man schreibt dauernd den selben code. 20 mal assign, 17 mal op=, 25 vergleichsoperatoren, etc. alles ist trivialst als non member loesbar. Man braucht fast keine member funktionen um eine klasse funktionsfaehig zu machen.

    und selbst wo man member bzw. friends braucht, ist eine non member funktion trotzdem genial, auch wenn sie nur forwarded - weil es eben die kapselung soviel verbessert.

    generell sind member funktionen furchtbar. sie sind ein notwendiges uebel, aber non member sind einfach soviel besser.


Anmelden zum Antworten