wrapper klasse, rückgabetyp vom operator= konstante referenz?



  • Der Titel sagt es eigentlisch schon.
    Wenn ich ein Wrapper-Klassentemplate habe

    template<class T>
    class Wrapper
    {
    public:
       T member;
    
       ...
    };
    

    kann ich dann den operator= der wie folgt aussieht eine konstante refrerenz zurückgeben lassen ? wieso sieht man immer in beispielen nur normale referenzen?

    ...
       T& operator=(const T& other) { ... }
    
       // oder
    
       const T& operator=(const T& other) { ... }
       ...
    

    Wäre über Hilfe dankbar.



  • Um das nochmal klar zu stellen, ich kann mir denken dass wahrscheinlich beides möglich ist. Aber ich hätte gerne die Vor- und Nachteile beider Varianten gewusst :>



  • das geht.
    man sieht nur normale, weil wir zu faul sind, da const hinzuschreiben.

    ohne const ists genau wie bei int.
    (i=5)=4;
    geht.

    mit const geht der quatsch nicht mehr.


  • Mod

    prinzipiell gibt es keine besonderen einschränkungen hinsichtilich der signatur des operators=, außer dass er grundsätzlich genau einen parameter (neben dem objektparameter, op= ist immer eine nicht statische memberfunktion) hat. const, volatile, (rein) virtuell: alles ist möglich. allerdings kommt einem op=, der eine (evtl. cv-qualifizierte) referenz auf ein objekt dieser klasse nimmt, besondere bedeutung zu. jede klasse hat einen solchen operator (wenn man ihn nicht selbst deklariert, macht der compiler das und verdeckt damit jeden op= aller basisklassen). die signatur des implizit deklarierten operators= einer klasse A ist dabei entwader

    A& operator=(const A&)
    

    oder

    A& operator(A&)
    

    welche version gewählt wird, hängt von den copy-ops der basisklassen und member ab.
    warum wird nun eine referenz zurückgegeben? damit wird das verhalten des zuweisungsoperators für primitive typen auf klassen übertragen. für eingebeute typen ist das ergebnis einer zuweisung ein (non-const) lvalue, das auf den linken operanden verweist (in C ist es ein rvalue). prinzipiell ist es dir freigestellt, eine const-referenz oder auch void oder irgendein Foo zurückzugeben. es schränkt dann aber möglicherweise die verwendung ein, ohne dass diese einschränkung irgendwie wirklich nützlich wäre.

    Edit:

    int i;
    (i=5)=4;
    

    ist well-formed, produziert aber undefiniertes verhalten.

    struct Foo {};
    Foo a={}, b={}, c={};
    (a=b)=c;
    

    ist dagegen wohldefiniert (wenn auch nicht besonders nützlich). interesanter sind dann aber fälle, in denen =c etwa durch einen aufruf einer memberfunktion ersetzt wird. etwa:

    (*getFooPtr()=foo).bar(); // getFooPtr sei ein komplexer ausdruck oder eine komplexe funktion
    

    hier ist es nicht unbedingt sinnvoll, das ganze in zwei statements aufzusplitten - will man den overhead der doppelten auswertung von getFooPtr vermeiden, müsste man nur dafür noch eine referenz oder einen pointer einführen. das erhöht nicht unbedingt die lesbarkeit.



  • camper schrieb:

    (*getFooPtr()=foo).bar(); // getFooPtr sei ein komplexer ausdruck oder eine komplexe funktion
    

    hier ist es nicht unbedingt sinnvoll, das ganze in zwei statements aufzusplitten - will man den overhead der doppelten auswertung von getFooPtr vermeiden, müsste man nur dafür noch eine referenz oder einen pointer einführen. das erhöht nicht unbedingt die lesbarkeit.

    Das ist wohl einigermaßen Ansichtssache. Ich persönlich kann schneller 10+ klar und simpel geschriebene Zeilen lesen und verstehen als eine einzige vollgestopft mit kryptischem Kauderwelsch.



  • Vielen Dank für eure hochqualifizierten Antworten 🙂
    Da ich eine Klasse schreibe, die Properties in C++ emulieren soll, will ich dass sich diese property Klasse auch anfühlt wie ein 'T'.

    In einem Fall ist es also besser eine nicht-konstante Referenz zurückzugeben.

    Eine andere Frage:
    Ich will natürlich auch andere Operatoren wie <<, ++, += usw. überladen, damit sich die Klassen wirklich wie ein 'T' anfühlt.

    Gibt es irgendwelche Referenzen oder Richtlinien nachdennen ich herausfinden kann wo wann welcher Parameter / Rückgabewert konstant usw. sein soll oder nicht?

    Ich bin noch sehr viel am Ausprobieren und Rumprobieren 😉
    Dass heißt, es ist noch lääängst nicht fertig.

    Im moment läuft es schon so, dass der Anwender nur dann den =operator aufrufen kann, wenn auch eine set funktion existiert, d.h. es im property "erlaubt" ist.

    Mein größtest Problem ist, wie ich die Operatoren so implementiere, dass sie möglichst sich genau so verhalten wie ein Object der Klasse 'T'.

    Meine Aufbau sieht im Moment so aus:

    #ifndef _PROPERTY_INCLUDED_
    #define _PROPERTY_INCLUDED_
    
    // O -> Owner Class (Car ... usw.)
    // T -> Value Type (int, std::string ... usw.)
    // P -> Property Class (_Speed, _Password ... siehe unten)
    template<class O, typename T, class P>
    class property : private P
    {
    public:
    	typedef O	owner_class;
    	typedef T	value_type;
    	typedef P	property_class;
    
    public:
    	friend owner_class; // hierdurch kann die Owner Klasse auf value zugreifen
    
    public:
    	//! standard constructor
    	property() {}
    
    	//! destructor
    	~property() {}
    
    private:
    	//! constructor by value
            //  implicit ?
    	template<class U> explicit property(const U value) { property_class::value = value; }
    
    public:
    	//! get wrapper
    	const T& get() { return P::get(); }
    
    	//! set wrapper
    	void set(const T& other) { P::set(other); }
    
    public:
    	//! copy-assignment operator (returns const value)
    	//  this one gets called if there is no get()
    	template<class U> const T operator=(const U other)
    	{
    		set(other);
    		return other; 
    	}
    
    	//! copy-assignment operator (returns const value)
    	//  this one gets called if there is a get()
    	template<class U> const T& operator=(const U other)
    	{
    		set(other);
    		return get(); 
    	}
    
    	//! const cast operator
    	template<class U> operator const U()
    	{
    		return get();
    	}
    
    	//! compare operator
    	template<class U> bool operator==(const U& other)
    	{
    		return get() == other;
    	}
    
    	//! not compare operator
    	template<class U> bool operator!=(const U& other)
    	{
    		return get() != other;
    	}
    
    	/* something like this?
    
    	template<class U, class W> U operator<<(W other)
    	{
    		return get().operator<<(other);
    	}
    
    	template<class U, class W> U operator>>(W other)
    	{
    		return get().operator>>(other);
    	}
    	*/
    };
    
    // just a test solution, to test cout
    template
    <
    	class O,
    	class T,
    	class P,
    	class _Elem,
    	class _Traits
    >
    inline std::basic_ostream<_Elem, _Traits>&
    	operator <<(std::basic_ostream<_Elem, _Traits>& _Ostr, property<O,T,P>& prop)
    	{
    		_Ostr << prop.get();
    		return (_Ostr);
    	}
    
    #endif //_PROPERTY_INCLUDED_
    

    Benutzen lässt sich das wie folgt:

    class Car
    {
    public:
       class _Speed
       {
       protected:
          int value;
       public:
          void set(const int& value) { this->value = value; }
          const int& get() const { return this->value; }
       };
       class _Password
       {
       protected:
          int value;
       public:
          void set(const int& value) { this->value = value; }
          // no get function
       };
    
       property<Car, int, _Speed> Speed;
       property<Car, int, _Password> Password;
    
    public:
       Car()
          : Speed(0), Password("some standard secret password")
       {}
    
       Car(const int speed, const std::string& pw)
          : Speed(speed), Password(pw)
       {}
    
    private:
       void some_private_function()
       {
          Speed.value = 2000;
       }
    };
    
    int main()
    {
       Car c(100);
    
       c.Speed = 200;
       std::cout << c.Speed << std::endl;
    
       c.Password = "blub";
    
       return 0;
    }
    

    ps: ich schreib das ganze nur aus spaß, da ich keine lust habe sowas wie:

    c.setSpeed(c.getSpeed() + 100);
    

    zu schreiben, stattdessen soll:

    c.Speed += 100;
    

    möglich sein.

    Die Klasse kann dank der Friend Beziehung auf den protected Member "value" und den Konstruktor mit einem T als Parameter zugreifen, falls ein Member "value" existiert.

    Es gibt auch Fälle, in dennen kein "value" existiert, z.b.:

    ...
    class _Time
    {
    public:
       const float get() const { return MyCAPI_Call_GetTime(); }
       void set(const float p) { MyCAPI_Call_SetTime(p); }
    };
    property<Car, float, _Time> Time;
    ...
    

    Diese Properties kann die Eigentümer Klasse dann z.b. so initialisieren

    Car::Car()
    : Time()
    {
    }
    

    Wenn was anderes versucht wird, Fehler 🙂



  • Du weißt das class und typename Parameter bei Templates das selbe tun? 🙂



  • David_pb schrieb:

    Du weißt das class und typename Parameter bei Templates das selbe tun? 🙂

    Ja, ich benutze class um auszudrücken, dass es eine Klasse sein sollte und typename für allgemeine Typen.


Anmelden zum Antworten