operator- (unary): pass-by-value oder pass-by-ref-to-const?
-
Hallo,
ich versuche gerade etwas die "basics" aufzufrischen und hänge gleich schon wieder bei einer Frage:
Welche der folgenden Versionen des unären Minus-Operators ist denn zu bevorzugen, aufgrund von besserer Performance o. Ä.? Meine Überlegungen waren, dass Version 1 evtl. besser bei temporären Objekten ist, bzw. meine ich gelesen zu haben: Wenn man eine Kopie anfertigen möchte, dann am Besten gleich bei der Parameterübergabe. Jetzt bin ich mir aber doch nicht sicher...
struct Vec3 { // kurz gehalten float x, y, z; }; // Version 1 Vec3 operator-(Vec3 vec) { vec.x = -vec.x; vec.y = -vec.y; vec.z = -vec.z; return vec; } // Version 2 Vec3 operator-(const Vec3& vec) { return {-vec.x, -vec.y, -vec.z}; }
Würde mich über eine kurze Antwort sehr freuen.
LG
-
Dein Lieblingscompiler macht aus beidem genau das gleiche.
-
Nimmt mich auch wunder. Bisher berücksichtige ich nur die Grösse, von der ich annehme, dass sie "übergeben" wird. https://ideone.com/t8R44O
Zu temporären Objekten und "pass-by-ref-to-const" oder "
entgegennahmeals const reference" wurde geantwortet:@swordfish sagte in Frage zu verfügbarem Stack:
A temporary bound to a reference parameter in a function call (§5.2.2 [expr.call]) persists until the completion of the full expression containing the call.
Ob es noch Weiteres zu berücksichtigen gibt (und ob so alles stimmt), nimmt mich ebenfalls wunder.
-
Ob nun bei der Parameterübergabe oder bei der Rückgabe kopiert wird ist doch völlig wumpe.
-
@swordfish Kann ich jetzt so leider nicht bestätigen (gcc version 8.1.0 x86_64-posix-seh-rev0) mit g++ -O3 -S test.cpp.
Mein verwendetes Testprogramm:
#include <iostream> struct Vec3 { float x, y, z; Vec3(float x, float y, float z) : x{x}, y{y}, z{z} { std::cout << "constructor" << std::endl; } ~Vec3() { std::cout << "destructor" << std::endl; } Vec3(const Vec3& other) { std::cout << "copy constructor" << std::endl; } Vec3& operator=(const Vec3& other) { std::cout << "copy assignment" << std::endl; return *this; } Vec3(Vec3&& other) { std::cout << "move constructor" << std::endl; } Vec3& operator=(Vec3&& other) { std::cout << "move assignment" << std::endl; return *this; } }; #define TOGGLE 0 #if TOGGLE Vec3 operator-(const Vec3& vec) { return {-vec.x, -vec.y, -vec.z}; } #else Vec3 operator-(Vec3 vec) { vec.x = -vec.x; vec.y = -vec.y; vec.z = -vec.z; return vec; } #endif int main() { Vec3 vec(0, 0, 0); Vec3 vec2 = -vec; Vec3 vec3 = -Vec3(0, 0, 0); }
Nach erneuter Recherche glaube ich, dass bei Version 1 immer gemoved wird, und bei Version 2 kann RVO eingesetzt werden (was nicht bei Funktionsargumenten geht), deshalb ist Version 2 theoretisch besser als Version 1.
Kann das jemand bestätigen oder habe ich noch einen Denkfehler drin?
-
Ja, ein riesen unterschied:
foo(Vec3 const&): movss xmm1, DWORD PTR [rsi+4] movss xmm0, DWORD PTR [rsi+8] mov rax, rdi movss xmm3, DWORD PTR .LC0[rip] movss xmm2, DWORD PTR [rsi] xorps xmm1, xmm3 xorps xmm0, xmm3 xorps xmm2, xmm3 movss DWORD PTR [rdi+4], xmm1 movss DWORD PTR [rdi], xmm2 movss DWORD PTR [rdi+8], xmm0 ret bar(Vec3): movss xmm0, DWORD PTR [rsi] movss xmm1, DWORD PTR .LC0[rip] mov rax, rdi xorps xmm0, xmm1 movss DWORD PTR [rsi], xmm0 movss xmm0, DWORD PTR [rsi+4] xorps xmm0, xmm1 movss DWORD PTR [rsi+4], xmm0 movss xmm0, DWORD PTR [rsi+8] xorps xmm0, xmm1 movss DWORD PTR [rsi+8], xmm0 ret
-
Ich finde, es ist eine Frage der Schnittstelle. Intern wirds vermutlich auf den gleichen Code hinauslaufen. Als Schnittstelle würde ich aber meist "ref to const" bevorzugen. Jetzt nicht unbedingt in diesem speziellen Fall, aber ich seh auch nicht, was hier dagegen spricht.
Ob irgendwo irgendwas kopiert wird, ist oft ein Implementierungsdetail. So ein Operator ist vielleicht nicht das beste Beispiel, aber stell dir vor, die Funktion wär einfach etwas länger, und mittendrin ist ein if, das aussteigt, ohne eine Kopie gemacht zu haben. Deswegen find ichs schon mal konsistenter, meist als const& zu übergeben, wenn nicht was anderes dagegen spricht.
Ehrlich gesagt würds mich auch etwas stören, das anders zu machen. Allein schon, weil das für mich ein gewisses Signal darstellt, hier ist es besser, eine Kopie zu übergeben (die dann z.B. gemoved wird), und genau die Fälle will ich dann auch möglichst auf den ersten Blick erkennen.