Parameterübergabe R-Value und const-reference
-
Hallo @all,
ich finde im Netz wiederkehrend widersprüchliche Aussagen welches der folgenden
Lösungen mein Problem am besten löst.
Zum Problem:
Ich habe ein Thread, wobei seine Parameter beim Start kopiert bzw. verschoben werden müssen, da evtl. sonst die Lebenszeit des Threads, welcher diesen Thread startet, nicht lang genug ist. Um es dem Nutzer freizustellen, welche Version er nutzen möchte, benötige ich eine Version, die entscheidet, ob kopiert oder verschoben werden soll./* Mein erster Ansatz unterscheidet bei den Funktionen zwischen const-reference und R-Value, wobei die const-reference eine Kopie erzeugt und dann die R-Value Version mit dieser Kopie aufruft. Problem: Bei n Parametern benötige ich 2^n Funktionen */ void foo( std::string&& ) { // Do Stuff } void foo( const std::string& s ) { foo( std::move( std::string( s ) ) ); } /* Hierbei sei es dem Nutzer überlassen, ob er mittels std::move den Parameter übergibt oder eine Kopie-erstellt. Problem: Möchte ich den String in der Funktion nochmals verschieben o.Ä., also muss ich nochmals std::move aufrufen, ich habe also effektiv immer 1 move-Konstruktor Aufruf + 1 Move oder Copy Aufruf */ void foo( std::string s ) { //Do Stuff } /* Erscheint mit recht aufwendig und ich weiß nicht 100% ob ich das richtig verwendet habe */ template < class T, class = typename std::enable_if<std::is_is_convertible<T,std::string>::value>::type> void foo( T&& ) { // Do Stuff }
Soweit meine Ansätze. Evtl. gibt es auch noch eine andere? Welche von den ist sonst die zu empfehlene?
MfG
shft
-
Nimm einfach die zweite Variante, ein move kostet dich irgendwie 4-8 instructions oder so. Jedenfalls voellig irrelevant in einem Kontext wo du gerade einen extra Thread startest. Ich wuerde die zweite Variante auch sonst fast ueberall nutzen, ausser du hast wirklich einen guten Grund es nicht zu tun.
-
Es gibt einen Vortrag von Herb Sutter da werden verschiedene Möglichkeiten diskutiert. Siehe hier, etwa ab Minute 55: https://www.youtube.com/watch?feature=player_detailpage&v=xnqTKD8uD64#t=3300
-
Danke, das Video ist echt sehr hilfreich
-
shft schrieb:
Ich habe ein Thread, wobei seine Parameter beim Start kopiert bzw. verschoben werden müssen, da evtl. sonst die Lebenszeit des Threads, welcher diesen Thread startet, nicht lang genug ist. Um es dem Nutzer freizustellen, welche Version er nutzen möchte, benötige ich eine Version, die entscheidet, ob kopiert oder verschoben werden soll.
Dafür brauchst du gar keine zwei Versionen. Das Kopieren/Moven machst nicht Du sondern der Nutzer bzw die thread-Library selbst. Der Parameter-Typ den, du für deine Funktion wählst, die du in einem neuen Thread ausführst, ist davon nahezu unabhängig.
shft schrieb:
void foo( const std::string& s ) { foo( std::move( std::string( s ) ) ); }
Das
std::move
ist hier überflüssig, da std::string(s) ja eh schon ein Rvalue ist.shft schrieb:
/* Problem: Möchte ich den String in der Funktion nochmals verschieben o.Ä., also muss ich nochmals std::move [...] */
Du willst also einen
eigenen
std::string in der Funktion haben? Dann nimm doch einfachvoid foo(std::string meineigenerstring) { … }
Ob das Ding kopiert oder gemoved wird, hängt davon ab, ob der Aufrufer
std::thread
bzwstd::async
einen Lvalue-string oder Rvalue-string übergibt. Und da wir wissen, dassstd::string
einen sehr effizienten Move-Konstruktor hat, können wir uns hier auch die Sache mit der Überladung sparen. Das einzige, was wir uns damit sparen würden, wäre nämlich ein Move, aber der ist ja billig beistd::string
.Und auch wenn Du das hier schreibst:
void foo(const std::string& constref) { … }
musst Du dir keine Sorgen über Lebenszeiten machen, weil bei Aufrufen wie
std::thread (foo, std::string("temp"));
std::thread die Argumente immer in den anderen Thread kopiert/moved. Wenn Du einem neuen Thread explizit eine Referenz übergeben willst, ginge das so:
void foo(std::string& nonconstref) { nonconstref = "blupp"; } int main() { string blah; std::thread t (foo, std::ref(blah)); t.join(); std::cout << blah << std::endl; }
Man muss hier also extra
std::ref
hinschreiben, damit derstring
nicht gemoved oder kopiert wird.Wenn Dir das nicht ganz geheuer ist, kannst du das natürlich auch per Lambda-Ausdruck machen. Dann klappt das mit der Überladung eh besser, falls Du sowas wirklich willst:
void foo(std::string meins) { … } int main() { string blah = "hello world"; std::thread t ( [blah = move(blah)] { foo(move(blah)); } ); // C++14 t.join(); }
Der String zieht zunächst in das Lambda-Objekt um, welches dann in den neuen Thread umzieht und dann den string weiter an foo übergibt ohne irgendwo den
string
jemals zu kopieren. Aber das ist wirklich äquivalent zuvoid foo(std::string meins) { … } int main() { string blah = "hello world"; std::thread t (foo, move(blah)); t.join(); }
Der einzige Vorteil, den Dir hier ein Lambda-Ausdruck bietet, ist, dass Du damit besser klarkommst, wenn
foo
tatsächlich überladen wurde.HTH
kk