Designfrage: Return-Werte als Tuple oder Struct?



  • So, ich bin gerade dabei eine Library zu programmieren, in der es auch Klassen gibt, die eine Position (X/Y) und Größe (Höhe/Breite) haben. Entsprechend gibt es z.B. folgende Methoden:

    void position(int x, int y);
    

    Nun, jetzt ist die Frage, was bevorzugt ihr, wenn man die Position abfragen will, wo es ja mehrere Werte zurück gibt? Eher ein Tuple oder eher eine Struct? Hat alles seine Vor- und Nachteile.

    boost::tuples::tuple<int, int> position();
    
    struct point{ int x, int y};
    // ...
    point position();
    

    Bei point sehe ich den Vorteil, das man dann wirklich weiß was x und was y ist. Bei den Tuples hab ich zwar auch zwei ints, aber man kann ja sehr leicht x und y verwechseln?

    Mich würden gerne mehr Pros und Contras interessieren. Oder ist es euch egal? Oder habt ihr einen Favoriten? Oder ist keine meiner Lösungen optimal?



  • Wenn ich mehrmals die gleiche Sache zurückgeben muss verwende ich Structs/Klassen; wenn ich jedoch nur einmal irgendwas zurückgeben muss, verwende ich (z.B.)

    pair<int, double>
    

    Vorteil pair: mann muss nicht dauernd für jeden Scheiß ein neues Struct definieren.

    Vorteil Struct: Jeder sieht gleich, worum es geht (z.b. point.x statt point.first) und du kannst es jederzeit auch erweitern, z.B einen operator+ definieren...

    In deinen Fall nimm Struct.



  • Ich würde in jedem Fall ein struct nehmen, viel Mehraufwand hast du für die Definition ja nicht.

    Um die Frage ein bisschen auszuweiten, was spricht gegen getX/getY/getWidth/getHeight statt getPosition/getSize?



  • getX() usw. macht das Interface der Klasse viel zu groß, das stört mich pers. ziemlich. Bei Tuples und Structs reicht mir eine getter. Bei Structs kann ich das dann so abfragen:

    int x = myObject->position().x;
    int y = myObject->position().y;
    

    Werde mich dann der Mehrheit hier richten und structs verwenden.

    Danke euch!



  • Artchi schrieb:

    Werde mich dann der Mehrheit hier richten und structs verwenden.

    aber besser so:

    void position (point *p)
    {
      p->x = ...;
      p->y = ...;     
    }
    
    ...
      point p;
      position (&p);
    ...
    

    damit vermeideste kopieroperationen der ganzen struct. bei 'point' zwar nicht so schlimm, aber bei grösseren structs wär's schon blöd. ausserdem haste dann noch den rückgabewert zur freien verwendung



  • @net
    Deine Lösung halte ich nicht für sonderlich schön. Mit return Wert kannst du die Funktion als Operand in Ausdrücken verwenden, mit deiner Variante geht das nicht soo einfach.
    Übrigens, hast du schon mal was von RVO und NRVO gehört?

    Artchi schrieb:

    getX() usw. macht das Interface der Klasse viel zu groß, das stört mich pers. ziemlich.

    kann ich durchaus nachvollziehen, würde dir aber trotzdem empfehlen, einzelne Funktionen dafür zu machen. Bei mir sieht dein Beispiel dann ungefähr wie folgt aus

    int x = myObject->position_x();
    int y = myObject->position_y();
    

    Das ermöglicht dir die Funktionen effektiver zu nutzen. Wozu x und y ermitteln, wenn du zB nur x brauchst? Eine position() Funktion kannst du ja trotzdem noch definieren, falls du beide Werte benötigst.

    Bleibst du bei deiner Lösung, dann machs aber zumindest mit einem Aufruf, also

    point pt = myObject->position();
    int x = pt.x;
    int y = pt.y;
    

    Zweimal position() aufzurufen ist nicht sonderlich toll, erst recht, wenn du dort irgendwelche API Aufrufe drin hast.



  • template<typename T1, typename T2>
    class PairAssign
    {
    public:
      PairAssign(T1 & v1, T2 & v2) : v1_(v1), v2_(v2) {}
    
      template <typename R1, typename R2>
      std::pair<R1,R2> const& operator= (std::pair<R1,R2> const& pair)
      {
        v1_ = pair.first;
        v2_ = pair.second;
        return pair;
      }
    
    private:
      T1 & v1_;
      T2 & v2_;
    };
    
    template <typename T1, typename T2>
    pairAssigner(T1 & v1, T2 & v2)
    {
      return PairAssign<T1,T2>(v1,v2);
    }
    
    Damit ginge dann:
    
    int x,y;
    pairAssigner(x,y) = myObject->getPosition();
    

    Ist nur ne Idee, nicht getestet etc.
    Sieht aber ganz nett aus.

    MfG Jester



  • groovemaster schrieb:

    @net
    Übrigens, hast du schon mal was von RVO und NRVO gehört?

    nö, aber danke für den hinweis 🙂



  • Jester: In Boost heißt das boost::tie. (Ich komme mir aber immer etwas komisch vor, wenn ich mit der boostschen Template-Artillerie auf Spatzen schieße... 🙂 )



  • Ein X/Y-Paar was eine Position darstellt sollte imho auch als Position zurückgegeben werden und nicht einzeln als x und y...

    Am besten speichert man intern auch gleich die Position als Point und nicht getrennt. Dann macht man einfach einen getter position() der eine konstante Referenz auf die interne Position zurückliefert.

    Wenn jetzt auch noch width und height hinzukommt, am besten gleich als Rect abspeichern.



  • net schrieb:

    groovemaster schrieb:

    @net
    Übrigens, hast du schon mal was von RVO und NRVO gehört?

    nö, aber danke für den hinweis 🙂

    hey, ich hab mal geguckt was das ist. dabei wird der copy constructor wegoptimiert. das hift aber nicht viel, wenn man sowas hat:

    struct fette_struct 
    {
       int fett[0x10000];
    }; 
    
    fette_struct get_fett()
    {
       fette_struct voll_fett;
       return voll_fett;
    }
    
    ...
      fette_struct echt_fett = get_fett();
    ...
    

    dabei wird die gesamte struct rüberkopiert. rvo kann das doch nicht optimieren oder täusche ich mich da 😕



  • @net
    Schau dir mal das an.



  • groovemaster schrieb:

    @net
    Schau dir mal das an.

    man dankt 🙂


Anmelden zum Antworten