Rückgabeeffizienz/RVO



  • tomate~ schrieb:

    // Variante 2
    template <class T>
    Vector3<T> Vec3Normalize2(const Vector3<T>& v) {
    	float length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z);
    
    	if(length > 0) {
    		float invLength = 1.0f / length;
    		return Vector3<T>(v * invLength);
    	}
    	return Vector3<T>::ZERO;
    }
    

    Was liefert denn der Ausdruck v*invLength? Wenn das schon ein Vector3<T> ist, dann kannst Du dir das Vector3<T>() drum herum sparen. Denn Du legst von dem Ergebnis des Ausdrucks ja noch eine Kopie an. Der Compiler darf auch diese Extra-Kopie wegoptimieren. Sie scheint aber dennoch unnötig zu sein. Das, was "Vector3<T>(v * invLength)" aber erzeugt ist ein temporäres Objekt für welches man RVO anwenden kann. Compiler tun dies wahrscheinlich.

    Mich stören 2 Dinge an dem Code:
    1. Du benutzt float für length und invLength
    2. Vector3<T>::ZERO ist irgendwie überflüssig.

    So hätte ich es gemacht:

    Vector3<T>& Vector3<T>::equals_normnalize() // nicht-statische Elementfunktion
    {
      using std::sqrt;
      T const l2 = x*x+y*y+z*z;
      if (l2>0) {
        T const inv = sqrt(1/l2);
        x *= inv;
        y *= inv;
        z *= inv;
      }
      return *this;
    }
    
    ...
    
    template<typename T>
    Vector3<T> normalize(Vector3<T> const& x) // freie Funktion
    {
      Vector3<T> result (x);
      result.equals_normalize();
      return result; // NRVO anwendbar
    }
    

    Gruß,
    SP



  • sowsa sollte man in assembler machen, das lahme an dem code da oben sind natürlich diese ganzen c++ klassen mit ihren zig konstruktoren die da aufgerufen werden und objekte anlegen und zerstören nur um nen vektor zu normalisieren :roller:



  • Einen benutzerdefinierten Copy-Konstruktor braucht man hier genauso wenig wie einen Destruktor -- man kann und sollte sie also weglassen und dem Compiler überlassen. Wenn Du dann für T noch so etwas wie double oder float einsetzt, muss beim Kopieren gar nichts "aufgerufen" werden.

    Experiment: Ich übersetze das hier

    #include <cmath>
    
    class foo
    {
    	float x, y, z;
    public:
    	explicit foo(float x=0, float y=0, float z=0)
    	:x(x), y(y), z(z) {}
    
    	foo& equals_normalize()
    	{
    		using std::sqrt;
    		float f = sqrt(1/(x*x+y*y+z*z));
    		x*=f;
    		y*=f;
    		z*=f;
    		return *this;
    	}
    };
    
    inline foo normalize(foo const& x)
    {
    	foo r = x;
    	r.equals_normalize();
    	return r;
    }
    
    void blah(foo t);
    
    void dings(foo t)
    {
    	blah(normalize(t));
    }
    

    mit g++ -O3 -S -o assembler.s und schau Dir das Ergebnis per cat assembler.s | c++filt an. Hier ist der Abschnitt für dings():

    dings(foo):
    .LFB97:
    	pushl	%ebp
    .LCFI0:
    	movl	%esp, %ebp
    .LCFI1:
    	subl	$16, %esp
    .LCFI2:
    	flds	8(%ebp)
    	flds	12(%ebp)
    	flds	16(%ebp)
    	fld	%st(2)
    	fmul	%st(3), %st
    	fld	%st(2)
    	fmul	%st(3), %st
    	faddp	%st, %st(1)
    	fld	%st(1)
    	fmul	%st(2), %st
    	faddp	%st, %st(1)
    	fdivrs	.LC0
    	fsqrt
    	fmul	%st, %st(1)
    	fxch	%st(1)
    	fstps	-4(%ebp)
    	fmul	%st, %st(1)
    	fxch	%st(1)
    	fstps	-8(%ebp)
    	fmulp	%st, %st(1)
    	fstps	-12(%ebp)
    	movl	-12(%ebp), %eax
    	movl	%eax, 8(%ebp)
    	movl	-8(%ebp), %eax
    	movl	%eax, 12(%ebp)
    	movl	-4(%ebp), %eax
    	movl	%eax, 16(%ebp)
    	leave
    	jmp	blah(foo)
    

    Ich sehe hier kein call für einen Copy-C'tor Aufruf.

    So, tsukuhime, Du meinst also, Du kannst besseren Assembler-Code generieren, was? :p

    Gruß,
    SP



  • Man muss nicht mal Assembler benutzen, wenn man den MMX/SSE/3Dnow! Befehlssatz ausnutzen will. Dazu unterstützen die meisten Compiler an MMX/SSE/3Dnow! angelehnte "intrinsics".

    Gruß,
    SP



  • Danke für deine Antworten Sebastian Pizer.
    Ein paar Sachen sind mir nicht klar:
    Du schreibst in deinem Code: T const inv = sqrt(1/l2);
    1. Wenn ich jetzt einen Vector3<int> anlege, gibt das doch einen Compiler-Error, oder?

    2. Du schreibst wenn es geht sollte man keinen CopyCtor und Dtor selber schreiben. Für mich hört sich das fast so an, als sei das eine notwendige Bedigung, damit RVO und NRVO greift. Ist das so? Wird NIE eine RVO/NRVO gemacht, wenn ich selber einen CopyCtor oder Dtor schreibe?

    3. In meinem Normalize2 habe ich ja 2 returns und die geben unterschiedliche Objekte zurück. Ich dachte in so einem Fall ist NRVO (und RVO) unmöglich? Es klang bei dir so, als sollte es dennoch gehen?

    4. Ich habe Normalize2 etwas umgeschrieben, so dass es nur noch ein return hat:

    template <class T>
    Vector3<T> Vec3Normalize2(const Vector3<T>& v) {
    	float length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z);
    
    	Vector3<T> ret(Vector3<T>::ZERO);
    
    	if(length > 0) {
    		float invLength = 1.0f / length;
    		ret = v * invLength;
    	}
    	return ret;
    }
    
    // Aufruf A:
    Vec3f v(2,3,4);
    Vec3f v2 = Vec3Normalize2(v);
    
    // Aufruf B:
    Vec3f v(2,3,4);
    Vec3f v2;
    v2 = Vec3Normalize2(v);
    

    Wenn ich die Funktion wie in "Aufruf A" aufrufe, dann ist die Ausgabe:

    Ctor(x,y,z)
    CopyCtor()
    Ctor(x,y,z)
    Dtor()

    NRVO scheint also zu greifen. Rufe ich den Code hingegen wie in "Aurfuf B" auf, so ergibt die Ausgabe:

    Ctor(x,y,z)
    Ctor()
    CopyCtor()
    Ctor(x,y,z)
    Dtor()
    Dtor()

    Ich finde das ist eine stattliche Anzahl von Aufrufen, nur für das Normalisieren eines Vektors!
    Die Frage deshalb: Sollte ich nicht besser alle Rückgaben als Referenz im 1. Parameter zurückgeben? Also lieber das: "void MatrixInvert(m, m2)" anstatt von "m=MatrixInvert(m2)"?



  • 1. ausprobieren
    2. nö, ist keine bedingung
    3. also der (2008er) msvc kann das auf jeden fall - hab ma nen bsp gemacht, siehe ende.

    point hatte ich gerade zur hand, weils in nem anderen thread darum ging ^^
    __declspec(noinline) ist msvc spezifisch, weil ich nicht wollte, dass er die fkt inlined - dann wär es ja klar, dass copy ctor etc nich aufgerufen werden

    bsp.:

    #include <algorithm>
    #include <iostream>
    
    struct point
    {
    	float x, y;
    	explicit point(float _x = 0, float _y = 0) : x(_x), y(_y)
    	{
    		std::cout << "CTor" << std::endl;
    	}
    	point(const point &other) : x(other.x), y(other.y)
    	{
    		std::cout << "CopyCTor" << std::endl;
    	}
    
    	void swap(point &other)
    	{
    		using std::swap;
    		swap(x, other.x);
    		swap(y, other.y);
    	}
    
    	point& operator= (const point &other)
    	{
    		std::cout << "op=" << std::endl;
    		point tmp(other);
    		swap(tmp);
    		return *this;
    	}
    
    	~point()
    	{
    		std::cout << "DTor" << std::endl;
    	}
    };
    
    std::ostream& operator<< (std::ostream &s, const point &these)
    {
    	return s << these.x << ' ' << these.y;
    }
    
    point __declspec(noinline) test(float a, float b)
    {
    	if(b == 0)
    		return point(a, a);
    	if(a < 0)
    		return point(a, b);
    	else
    		return point(b, a);
    }
    
    int main()
    {
    	float a, b;
    	std::cin >> a >> b;
    	std::cout << test(a, b) << std::endl;
    	point x;
    	std::cout << (x = test(b, a)) << std::endl;
    }
    

    MSVC schrieb:

    2
    3
    CTor
    3 2
    DTor
    CTor
    CTor
    op=
    CopyCTor
    DTor
    2 3
    DTor

    ->

    // Eingaben
    
    // erste Variante:
        CTor
        //ausgabe
        DTor
    
    //zweite Variante:
        CTor //CTor mit (0,0)
        CTor //das eigentliche objekt, welches dem alten zugewiesen wird
        op= //zuweisung
          CopyCTor //tmp wird erstellt
          //geswapt
          DTor //tmp wird zerstört
        //ausgabe
        DTor
    

    PS:
    wieso braucht man die extra klammern hier eigtl:
    std::cout << (x = test(b, a)) << std::endl;



  • tomate~ schrieb:

    Du schreibst in deinem Code: T const inv = sqrt(1/l2);
    1. Wenn ich jetzt einen Vector3<int> anlege, gibt das doch einen Compiler-Error, oder?

    Möglich. Was soll denn das Ergebnis von normalize(Vector3<int>(1,2,3)) sein? :p

    tomate~ schrieb:

    2. Du schreibst wenn es geht sollte man keinen CopyCtor und Dtor selber schreiben. Für mich hört sich das fast so an, als sei das eine notwendige Bedigung, damit RVO und NRVO greift. Ist das so?

    Nein. Das hat nichts damit zu tun. Bei (N)RVO oder noch allgemeiner "copy elision" geht es um das Reduzieren von unnötigen Kopien. Ob man beim Kopieren einen benutzerdefinierten Konstruktor aufrufen muss oder nicht, ist egal. "copy elision" ist gerade dann interessant, wenn die Objekte nicht-triviale Copy C'tors und D'tors haben.

    tomate~ schrieb:

    3. In meinem Normalize2 habe ich ja 2 returns und die geben unterschiedliche Objekte zurück. Ich dachte in so einem Fall ist NRVO (und RVO) unmöglich? Es klang bei dir so, als sollte es dennoch gehen?

    Bei NRVO spielt das eine Rolle, ja. Stell Dir vor, der Compiler übergibt einfach einen Zeiger als zusätzlichen Parameter, der auf die Stelle zeigt, wo das Funktionsergebnis konstruiert werden soll. Du hast keine return-Anweisung benutzt, die eine lokale Variable benennt. Im ersten Fall wird ein temporäres Objekt erzeugt. Das kann direkt dort erzeugt werden, wo der Zeiger hinzeigt (RVO). Im zweiten Fall benennst Du ein lvalue-Objekt. Das ist kein besonders interessanter Fall: Das Ergebnisobjekt wird einfach kopiert.

    tomate~ schrieb:

    4. Ich habe Normalize2 etwas umgeschrieben, so dass es nur noch ein return hat:

    template <class T>
    Vector3<T> Vec3Normalize2(const Vector3<T>& v) {
    	float length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z);
    
    	Vector3<T> ret(Vector3<T>::ZERO);
    
    	if(length > 0) {
    		float invLength = 1.0f / length;
    		ret = v * invLength;
    	}
    	return ret;
    }
    
    // Aufruf A:
    Vec3f v(2,3,4);
    Vec3f v2 = Vec3Normalize2(v);
    
    // Aufruf B:
    Vec3f v(2,3,4);
    Vec3f v2;
    v2 = Vec3Normalize2(v);
    

    Wenn ich die Funktion wie in "Aufruf A" aufrufe, dann ist die Ausgabe:

    Ctor(x,y,z)
    CopyCtor()
    Ctor(x,y,z)
    Dtor()
    

    NRVO scheint also zu greifen.

    Wieso? Das "CopyCtor()" kommt sicherlich von Deiner Initialisierung von "ret" mit Vector3<T>::ZERO. Außerdem, was macht denn eigentlich "ret = v * invLength"? Überleg mal genau nach. Ich habe bisher keine Definition von operator* und/oder operator= gesehen, kann das also nicht beantworten. Es sieht aber verdächtig aus. Wenn ich raten müsste:

    template<typename T>
    Vector3<T> operator*(Vector3<T> const& v, float f)
    {
      return Vector3<T>(v.x*f, v.y*f, v.z*f);
    }
    

    "v * invLength" liefert also ein temporäres Objekt, welches per operator= dem Objekt ret zugewiesen wird (copy assignment).

    Rufe ich den Code hingegen wie in "Aurfuf B" auf, so ergibt die Ausgabe:

    Ctor(x,y,z)
    Ctor()
    CopyCtor()
    Ctor(x,y,z)
    Dtor()
    Dtor()
    

    Ich finde das ist eine stattliche Anzahl von Aufrufen, nur für das Normalisieren eines Vektors!

    Da bist Du selbst dran Schuld. Sehr wahrscheinlich sieht das so aus:

    Ctor(x,y,z)  // Konstruktion von v
    -----------------------------------------------------
    Ctor()       // Konstruktion von v2
    -----------------------------------------------------
    CopyCtor()   // Copy-Konstruktion von "ret(ZERO)"
    -----------------------------------------------------
    Ctor(x,y,z)  // Konstruktion von v*invLength ("tmp1")
                 // (RVO wurde angewendet)
    -----------------------------------------------------
                 // copy assignment: ret = tmp1
    -----------------------------------------------------
    Dtor()       // tmp1 wird zerstört
    -----------------------------------------------------
                 // Das Ergebnis von Vec3Normalize2(v) ist
                 // ein neues temporäres Objekt ("tmp2").
                 // Hier wurde NRVO angewendet, so dass
                 // tmp2 DASSELBE Objekt wie ret ist.
    -----------------------------------------------------
                 // copy assignment: v2 = tmp2;
    -----------------------------------------------------
    Dtor()       // tmp2 wird zerstört
    -----------------------------------------------------
    Dtor()       // v2 wird zerstört
    Dtor()       // v wird zerstört
    

    Code so wie der hier

    Vec3f v2;
    v2 = Vec3Normalize2(v);
    

    ist "bah pfui!" 😉 weil Du hier eine Default-Initialisierung gefolgt von einem Copy-Assignment hast wobei

    Vec3f v2 = Vec3Normalize2(v);
    

    eine Copy-Initialisierung ist, bei der "copy elision" angewendet werden kann.

    Die Frage deshalb: Sollte ich nicht besser alle Rückgaben als Referenz im 1. Parameter zurückgeben? Also lieber das: "void MatrixInvert(m, m2)" anstatt von "m=MatrixInvert(m2)"?

    Für so ein "Popel-Objekt", welches -- wenn Du Deine ganzen cout<<"blah"-Methoden wieder rausschmeißt -- trivial kopierbar ist sofern T trivial kopierbar ist, schadet das nur. Du gewinnst rein gar nichts damit. Du bekommst nur unleserlichen Code dadurch.

    Bei einem "fetten" Matrix-Objekt, welches seine Elemente zB intern in einem std::vector<double> verwaltet ist das etwas anderes -- aber auch nicht mehr lange, dank C++0x und Rvalue-Referenzen.

    Es bleibt zu hoffen, dass die Leute mit Einzug von C++0x einen etwas "funktionaleren" Stil an den Tag legen. Wie Howard Hinnant so schön (sinngemäß) sagte: Es gibt jetzt keinen guten Grund mehr, Referenzen für Ergebnisse zu übergeben.

    Gruß,
    SP



  • unskilled schrieb:

    PS:
    wieso braucht man die extra klammern hier eigtl:
    std::cout << (x = test(b, a)) << std::endl;

    Ich würde mal vermuten weil er sonst zunächst cout << x auswertet und dann dem zurückgegebenen stream einen point zuweisen will, was natürlich nicht geht.

    Ok, das bedeutet ich kann dutzende returns haben und jedes return gibt ein anderes Objekt zurück und RVO/NRVO wird dennoch klappen? Ist das garantiert beim MSVC 2008?

    Meine Frage:

    Ich finde das ist eine stattliche Anzahl von Aufrufen, nur für das Normalisieren eines Vektors!
    Die Frage deshalb: Sollte ich nicht besser alle Rückgaben als Referenz im 1. Parameter zurückgeben? Also lieber das: "void MatrixInvert(m, m2)" anstatt von "m=MatrixInvert(m2)"?

    steht übrigens noch im Raum;)



  • Ich finde das ist eine stattliche Anzahl von Aufrufen, nur für das Normalisieren eines Vektors!

    Die Frage steht gar nicht mehr offen - weniger gehen ja nun gar nicht mehr...

    @RVO/NRVO:
    imho kann kein compiler garantieren, dass das immer klappt...

    bb

    PS:

    Ich würde mal vermuten weil er sonst zunächst cout << x auswertet und dann dem zurückgegebenen stream einen point zuweisen will, was natürlich nicht geht.

    Nö, so ists nicht... Sonst hätte die Fehlermeldung anders gelautet. Damit ich sie dir nicht vorenthalte:

    MSVC schrieb:

    1>.\main.cpp(58) : error C2678: binary '<<' : no operator found which takes a left-hand operand of type 'point' (or there is no acceptable conversion)
    1> .\main.cpp(37): could be 'std::ostream &operator <<(std::ostream &,const point &)'
    1> while trying to match the argument list '(point, overloaded-function)'



  • tomate~ schrieb:

    Meine Frage: [...] steht übrigens noch im Raum;)

    Ich hoffe, Du hast meine Antwort nicht übersehen. 🙂



  • Sebastian Pizer schrieb:

    tomate~ schrieb:

    Meine Frage: [...] steht übrigens noch im Raum;)

    Ich hoffe, Du hast meine Antwort nicht übersehen. 🙂

    Hehe, nein.

    Aalso. Ein paar Dinge finde ich seltsam die du schreibst. In meinem Beispiel "Aufruf A" schreibe ich, dass hier NVRO offenbar angewandt wird, was du mit "Wieso?" in Frage stellst. Aber im weiteren Text bestätigst du dann doch, dass es NVRO ist.
    Mein operator* sieht genau so aus, wie du es dir gedacht hast. Ergo kann man diese Ausgabe:

    Vec3f v(2,3,4);
    Vec3f v2 = Vec3Normalize2(v);
    
    Ctor(x,y,z)  // v wird erzeugt
    CopyCtor()   //  Vector3<T> ret(Vector3<T>::ZERO);
    Ctor(x,y,z)  // das ist das Objekt, das in operator* erzeugt wird
    Dtor() // nach operator= wird das temp. Objekt erzeugt.
    

    genau so erklären wie du es beschrieben hast (siehe Kommentare)
    Aber es gibt eben KEINEN weiteren CopyCtor und Dtor, was ja darauf hinweist, DASS NVRO angewandt wurde. Richtig?

    Ich habe jetzt mal deinen Ansatz genommen, sprich:

    template <class T> 
    Vector3<T> Vec3Normalize(const Vector3<T>& v) { 
    	Vector3<T> ret(v);
    	ret.normalize();
    	return ret;
    }
    
    // Aufruf:
    Vec3f v(2,3,4);
    Vec3f v2 = Vec3Normalize(v);
    
    // Ausgabe:
    Ctor(x,y,z)
    CopyCtor()
    

    Das ist jetzt so ziemlich das Optimum, oder?

    Was mich als "Anfänger" einfach verunsichert: Überall höre ich, dass der Compiler (N)RVO benutzen KANN (und auch nur dann, wenn ich passenden Code schreibe). Ich will aber nicht ständig für den Compiler programmieren, so dass er (N)RVO betreiben kann. Außerdem will ich auch nicht vor Zeilen a la

    Matrix m;
    ...
    m = MatrixRotY(45);
    

    Angst haben müssen.
    Ich weiß auch nicht wie böse so eine Zeile ist (Meine Matrix hat 16 floats).
    Würdet ihr bei einer Matrix mit 16 floats ebenfalls die Ergebnisse per value zurückgeben?



  • tomate~ schrieb:

    Aalso. Ein paar Dinge finde ich seltsam die du schreibst. In meinem Beispiel "Aufruf A" schreibe ich, dass hier NVRO offenbar angewandt wird, was du mit "Wieso?" in Frage stellst.

    Entschuldige. Ich hatte "offenbar nicht angewandt" gelesen.

    tomate~ schrieb:

    ...

    // Ausgabe:
    Ctor(x,y,z)
    CopyCtor()
    

    Das ist jetzt so ziemlich das Optimum, oder?

    Ja, besser geht's nicht, ne? 🙂
    Vorteil: Du kannst mit ".normalize()" einen Vektor "in-place" normalisieren. 2 Funktionen sind hier also recht praktisch. Manchmal hat man schon einen Vektor, den man in-place normalisieren will und manchmal will man eine normalisierte Kopie haben. Allerdings sollte die Elementfunktion ".normalize" gut dokumentiert sein. Verändert sie Objekt und liefert eine Referenz auf sich oder erzeugt sie eine normalisierte Kopie und verändert das Objekt nicht? Deswegen hatte ich einen Namen gewählt, der das verdeutlichen sollte ("normalize_equals" in Anlehnung an die Operatoren +=, -=, *=, /=, ...). operator+ definiere ich auch immer gerne mittels operator+= ...

    inline foo operator+(foo const& a, foo const& b)  
      {
        foo r (a); r += b; return r; // setzt auf NRVO  
      }
    

    tomate~ schrieb:

    Was mich als "Anfänger" einfach verunsichert: Überall höre ich, dass der Compiler (N)RVO benutzen KANN (und auch nur dann, wenn ich passenden Code schreibe). Ich will aber nicht ständig für den Compiler programmieren, so dass er (N)RVO betreiben kann. Außerdem will ich auch nicht vor Zeilen a la

    Matrix m;
    ...
    m = MatrixRotY(45);
    

    Angst haben müssen.
    Ich weiß auch nicht wie böse so eine Zeile ist (Meine Matrix hat 16 floats).
    Würdet ihr bei einer Matrix mit 16 floats ebenfalls die Ergebnisse per value zurückgeben?

    Die Sorgen und Einwände sind berechtigt. Ich verlasse mich auch nicht 100%ig darauf, dass (N)RVO durchgeführt wird. In den Fällen, wo es einen richtig großen Unterschied macht, setzte ich NICHT darauf. Ich erwarte es aber trotzdem von einem guten Compiler.

    Gruß,
    SP



  • Schon mal vielen Dank für deine Antworten. Sie waren sehr hilfreich! 🙂

    Das Problem ist halt nur, dass wenn ich jetzt "kleine" Objekte (dazu zähle ich einen Vector3) per Value zurückgebe, aber eine Matrix by reference per Parameter, dann ist das inkonsistent und damit auch unschön:

    Vec3f v(2,3,4);
    Vec3f v2 = Vec3Normalize(v);  // Rückgabe per value
    
    Matrix4 m ;
    Mat4RotationY(m, 90);   // Rückgabe per parameter
    

    Jetzt muss der Anwender (und ich) meines Codes immer genau hinschauen, wie jede Funktion funktioniert. Würde ich hingegen IMMER non-built-in Typen im 1. Parameter per Reference zurückgeben, wäre es vllt syntaktisch nicht sonderlich schön, aber einheitlich:

    Vec3f v(2,3,4);
    Vec3f v2;
    Vec3Normalize(v2, v);  
    
    Matrix4 m;
    Mat4RotationY(m, 90);
    

    Ich denke du weißt was ich meine. Ich kann mich einfach nicht entscheiden, wie ich es machen soll 😞



  • Du könntest es so machen:

    struct matrix4f {
      float elements[4][4];
    };
    
    void make_rotation(vec3f axis, float angle, matrix4f & out);
    
    inline matrix4f make_rotation(vec3f axis, float angle) {
      matrix4f m;
      make_rotation(axis,angle,m);
      return m; // setzt auf NRVO
    }
    

    Wenn NRVO hier nicht angewendet wird, ist das kein Beinbruch. Man hat dann immer noch die Wahl, statt

    matrix4f m = make_rotation(......);
    

    einfach

    matrix4f m; make_rotation(......,m);
    

    zu schreiben. Das würde ich aber erst dann machen, wenn ich festgestellt habe, dass diese Änderung auch etwas bringt.

    Wenn Du schon ein Matrix-Objekt hast, dann solltest Du

    make_rotation(......,m);
    

    statt

    m = make_rotation(......);
    

    schreiben, weil Du damit -- unabhängig von (N)RVO -- eine Kopie sparst.

    Gruß,
    SP


Anmelden zum Antworten