Bringt es etwas Funktionen für const T& und T&& zu überladen?
-
Frage steht im Prinzip oben.
Bringt es einen Geschwindigkeitsvorteil, wenn man eine Funktion für L- und RValues zu überladen oder ist das egal?
-
Du hast letztlich diese drei Möglichkeiten:
void foo(const my_type& v); // copy void foo(const my_type&& v); //movetemplate <typename T> void foo(T&& v); // beides, siehe reference collapsingvoid foo(my_type v); // beides, braucht copy+move oder move+moveDie erste Variante braucht zwei Implementierungen. Bei der zweiten muss die Implementierung im Header stehen, und bringt die üblichen Template Probleme mit sich. (Schlechte Fehlermeldungen.)
Die dritte Variante ist etwas langsamer, aber wenn man davon ausgeht, dass move nahezu gratis ist, ist es wohl die beste Variante.
Insofern würde zu 1. raten, wenn man über move Performance besorgt ist, zum zweitem wenn's ne reine forward Funktion für alles ist und sonst zur dritten Variante.
Sobald C++17 raus ist und wir modules und concepts haben, sind die Template Probleme weg, dann rate ich immer zu 2. Aber das dauert halt noch.
-
soweit wie ich das bisher verstanden habe, sollte man wie bisher für "const t&" und zusätzlich für nonconst "T&&" überladen. der rvalue ist ein temp. Objekt, wo man nicht kopieren muß, sondern verschieben kann (move semantics, z.B. Pointer austauschen statt Inhalt kopieren), das temp. Objekt wird am Ende des "full expression" zerstört und nicht weiterverwendet
-
dd++: Du kannst nun beide Fälle mit einer Überladung abdecken, die den Parameter per Value als
Tnimmt (natürlich nicht const). Man sollte immer diese Variante wählen, ausser es spricht einer der von cooky451 genannten Gründe dagegen.In den Fällen, wo die in diesem Thread gestellte Frage überhaupt auftritt, geht es nur darum, Besitz an eine Funktion zu übertragen. Bei reinem Lesezugriff verwendet man weiterhin
const T&für grosse Objekte undTfür kleine.T&&(als Template oder nicht) braucht man ziemlich selten. Und zwar als Parameter bei Move-Konstruktoren, Move-Zuweisungsoperatoren und Forwarding-Funktionen. Als Rückgabetyp kommt es so gut wie gar nie vor.<
>
-
Bei move-assignment wird's spannend.

-
Nathan schrieb:
Frage steht im Prinzip oben.
Bringt es einen Geschwindigkeitsvorteil, wenn man eine Funktion für L- und RValues zu überladen oder ist das egal?Das kommt drauf an. In einigen Situationen hilft es, in anderen ist es sinnfrei. std::vector<T>::push_back ist beispielsweise so überladen, weil dadurch ggf eine unnötige Kopie verhindert werden kann. Bei solchen Templates ist das sinnvoll, weil T ja nicht move-optimiert sein muss und logisch gesehen man eine "Kopie" erzeugen will. An anderen Stellen kann man das mit den Referenzen aber auch sein lassen, hier z.B.:
class dings { public: explicit dings(string s); // <-- nix mit Referenz private: string bums; ::: }; dings::dings(string s) : bums(move(s)) , ::: { ::: }Eine unnötige Kopie gibt es hier nicht, da std::string ja move-optimiert ist. Aber mit einer Überladung oder Perfect Forwarding könnte man sich noch eine ggf unnötige Move-Konstruktion sparen. Aber das lohnt sich eigentlich nicht, weil 'ne Move-Konstruktion von std::string eh recht flott ist.
-
cooky451 schrieb:
Bei move-assignment wird's spannend.

Den Zuweisungsoperator könnte man theoretisch über Pass by Value implementieren. Aber bringt das überhaupt etwas?
Solche Definitionen werden vom Compiler wahrscheinlich weder als Copy- noch Move-Assignment erkannt...
X& X::operator= (X source) { swap(source); return *this; }<
>
-
Laut Standard ist es ein copy constructor. Aber "get-value-and-swap" funktioniert halt für copy und für move. Deswegen sagte ich ja, da wird's interessant. Es ist irgendwie elegant, aber ich weiß noch nicht 100%ig was ich davon halten soll.
-
eine gute Einführung in das Thema gibt es z.B. hier:
http://www.cprogramming.com/c++11/rvalue-references-and-move-semantics-in-c++11.html
ich habe hier mal zum Basteln ein kleines Testprogramm, viel Spaß!
#include <iostream> using namespace std; class MyInt { public: MyInt() { m_Value = 0; cout << "MyInt() "; Cout(); } MyInt(const MyInt& myCopy) { m_Value = myCopy.m_Value; cout << "MyInt(c& " << &myCopy << ") "; Cout(); } MyInt(MyInt&& myCopy) { m_Value = myCopy.m_Value; cout << "MyInt(&& " << &myCopy << ") "; Cout(); } MyInt(int i) { m_Value = i; cout << "MyInt(" << i << ") "; Cout(); } ~MyInt() { cout << "~MyInt() "; Cout(); } MyInt& operator=(const MyInt& myCopy) { m_Value = myCopy.m_Value; cout << "operator=(c& " << &myCopy << ") "; Cout(); return *this; } MyInt& operator=(MyInt&& myCopy) { m_Value = myCopy.m_Value; cout << "operator=(&& " << &myCopy << ") "; Cout(); return *this; } void setValue(int i) { m_Value = i; cout << "setValue(" << i << ") "; Cout(); } private: void Cout() { cout << m_Value << " " << this << endl; } int m_Value; }; MyInt f() { MyInt myInt; myInt.setValue(11); return myInt; } int main () { { MyInt myInt; } cout << "--- MyInt myInt;" << endl; { f(); } cout << "--- f();" << endl; { MyInt myInt(f()); } cout << "--- MyInt myInt(f());" << endl; { MyInt myInt1(7); MyInt myInt2(myInt1); } cout << "--- MyInt myInt1(7); MyInt myInt2(myInt1);" << endl; return 0; }
-
Danke, jetzt ist mir einiges klar geworden.
Ich kannte Movesemantik schon vorher, habe sie bisher aber nur benutzt und keine eigenen Beispiele geschrieben. Was Reference Collapsing etc. ist wusste ich schon vorher, aber trotzdem danke.
Ich werde in der Regel einfach foo(T obj) verwenden, nur bei großen Objekten (Container, Bilder, etc.) beide Funktionen schreiben.
lg
Nathan
-
Wie groß die Objekte sind ist dabei eigentlich eher uninteressant. (Jedenfalls wenn du vector<> für groß hälst - wenn du sizeof meinst, mein Fehler.) Da du ja movest, zählt nur wie viele Handles das Objekt hat, und wie oft die Funktion aufgerufen wird (ohne dass sie geinlined wird). Bei einem Handle auf 500MB macht's wahrscheinlich kaum einen Unterschied, bei 50 auf je 16 Byte schon.

-
Nein, ich meinte nicht sizeof.
Da fällt mir gerade was anderes ein:
Move bei Pointern ist ja im Prinzip nurptr = othr.ptr; othr.ptr = nullptr;oder?
Wie handhabst du das denn bei großen Objekten, die an Funktionen übergeben werden müssen?
-
Wie.. meinst du das jetzt? Welche Art von Groß?

-
Nathan schrieb:
Wie handhabst du das denn bei großen Objekten, die an Funktionen übergeben werden müssen?
Drück dich klarer aus. Wird der Besitz an die Funktion übergeben?
Falls ja: Klasse movable machen. Wenn das nicht geht oder immer noch teuer ist, Objekt in eine Movable-Klasse wie
std::unique_ptrstecken. In beiden Fällen Pass By Value.Falls nein: Const-Referenz wie in C++98.
-
Ich meinte Besitzübertragung.
Danke für die Erklärung Nexus.