DIE WICHTIGSTE FRAGE ZUERST:
Gibt es Best Practices für das Senden von Parametern in C ++, weil ich es wirklich nicht trivial finde?
Wenn Ihre Funktion das übergebene Originalobjekt ändern muss , damit nach der Rückkehr des Aufrufs Änderungen an diesem Objekt für den Aufrufer sichtbar sind, sollten Sie die lvalue-Referenz übergeben :
void foo(my_class& obj)
{
// Modify obj here...
}
Wenn Ihre Funktion das ursprüngliche Objekt nicht ändern und keine Kopie davon erstellen muss (mit anderen Worten, sie muss nur den Status beobachten), sollten Sie den Wertverweisconst an Folgendes übergeben :
void foo(my_class const& obj)
{
// Observe obj here
}
Auf diese Weise können Sie die Funktion sowohl mit lWerten (lWerte sind Objekte mit einer stabilen Identität) als auch mit rWerten (rWerte sind beispielsweise temporäre Werte oder Objekte, von denen Sie sich als Ergebnis eines Aufrufs bewegen möchten std::move()) aufrufen .
Man könnte auch argumentieren , dass für grundlegende Typen oder Arten , für das das Kopieren schnell ist , wie zum Beispiel int, booloder chargibt es keine Notwendigkeit , durch Verweis übergeben wird , wenn die Funktion einfach braucht um den Wert zu beobachten, und von Wert vorbei begünstigt werden soll . Das ist richtig, wenn keine Referenzsemantik benötigt wird, aber was ist, wenn die Funktion irgendwo einen Zeiger auf dasselbe Eingabeobjekt speichern wollte, damit beim zukünftigen Lesen dieses Zeigers die Wertänderungen angezeigt werden, die in einem anderen Teil von ausgeführt wurden Code? In diesem Fall ist die Referenzübergabe die richtige Lösung.
Wenn Ihre Funktion muss nicht das ursprüngliche Objekt ändern, sondern muss eine Kopie dieses Objekts speichern ( möglicherweise das Ergebnis einer Transformation des Eingangs zurückzukehren , ohne die Eingabe zu ändern ), dann könnte man erwägen nach Wert unter :
void foo(my_class obj) // One copy or one move here, but not working on
// the original object...
{
// Working on obj...
// Possibly move from obj if the result has to be stored somewhere...
}
Das Aufrufen der obigen Funktion führt immer zu einer Kopie, wenn l-Werte übergeben werden, und zu einer Bewegung, wenn r-Werte übergeben werden. Wenn Ihre Funktion dieses Objekt irgendwo speichern muss, könnten Sie einen zusätzlichen führen Umzug von ihm (zum Beispiel in dem Fall foo()ist eine Elementfunktion , dass der Bedarf den Wert in einem Datenelement zu speichern ).
Wenn Verschiebungen für Objekte vom Typ teuer sindmy_class , können Sie eine Überladung in Betracht ziehen foo()und eine Version für l-Werte (Akzeptieren eines l-Wert-Verweises auf const) und eine Version für r-Werte (Akzeptieren eines r-Wert-Verweises) bereitstellen:
// Overload for lvalues
void foo(my_class const& obj) // No copy, no move (just reference binding)
{
my_class copyOfObj = obj; // Copy!
// Working on copyOfObj...
}
// Overload for rvalues
void foo(my_class&& obj) // No copy, no move (just reference binding)
{
my_class copyOfObj = std::move(obj); // Move!
// Notice, that invoking std::move() is
// necessary here, because obj is an
// *lvalue*, even though its type is
// "rvalue reference to my_class".
// Working on copyOfObj...
}
Die oben genannten Funktionen sind so ähnlich, in der Tat, dass Sie eine einzelne Funktion draus machen könnten: foo()könnten eine Funktion werden Vorlage und Sie könnten verwenden perfekte Weiterleitung zu bestimmen , ob eine Bewegung oder eine Kopie des Objekts wird intern weitergeleitet werden generiert:
template<typename C>
void foo(C&& obj) // No copy, no move (just reference binding)
// ^^^
// Beware, this is not always an rvalue reference! This will "magically"
// resolve into my_class& if an lvalue is passed, and my_class&& if an
// rvalue is passed
{
my_class copyOfObj = std::forward<C>(obj); // Copy if lvalue, move if rvalue
// Working on copyOfObj...
}
Vielleicht möchten Sie mehr über dieses Design erfahren, indem Sie sich diesen Vortrag von Scott Meyers ansehen (beachten Sie jedoch, dass der von ihm verwendete Begriff " Universal References " nicht dem Standard entspricht).
Eine Sache, die Sie beachten sollten, ist, dass dies std::forwardnormalerweise zu einer Bewegung für r-Werte führt. Auch wenn es relativ unschuldig aussieht, kann das mehrfache Weiterleiten desselben Objekts zu Problemen führen - zum Beispiel das zweimalige Bewegen von demselben Objekt! Achten Sie also darauf, dies nicht in eine Schleife zu setzen und dasselbe Argument in einem Funktionsaufruf nicht mehrmals weiterzuleiten:
template<typename C>
void foo(C&& obj)
{
bar(std::forward<C>(obj), std::forward<C>(obj)); // Dangerous!
}
Beachten Sie auch, dass Sie normalerweise nicht auf die vorlagenbasierte Lösung zurückgreifen, es sei denn, Sie haben einen guten Grund dafür, da dies das Lesen Ihres Codes erschwert. Normalerweise sollten Sie sich auf Klarheit und Einfachheit konzentrieren .
Die oben genannten sind nur einfache Richtlinien, aber meistens weisen sie Sie auf gute Entwurfsentscheidungen hin.
BETREFFEND DEN REST IHRES POSTES:
Wenn ich es als [...] umschreibe, gibt es 2 Züge und keine Kopie.
Das ist nicht richtig. Zunächst kann eine r-Wert-Referenz nicht an einen l-Wert gebunden werden. Dies wird daher nur kompiliert, wenn Sie einen r-Wert vom Typ CreditCardan Ihren Konstruktor übergeben. Zum Beispiel:
// Here you are passing a temporary (OK! temporaries are rvalues)
Account acc("asdasd",345, CreditCard("12345",2,2015,1001));
CreditCard cc("12345",2,2015,1001);
// Here you are passing the result of std::move (OK! that's also an rvalue)
Account acc("asdasd",345, std::move(cc));
Aber es wird nicht funktionieren, wenn Sie versuchen, dies zu tun:
CreditCard cc("12345",2,2015,1001);
Account acc("asdasd",345, cc); // ERROR! cc is an lvalue
Weil cces sich um einen l-Wert handelt und r-Wert-Referenzen nicht an l-Werte gebunden werden können. Darüber hinaus wird beim Binden einer Referenz an ein Objekt keine Verschiebung ausgeführt : Es handelt sich lediglich um eine Referenzbindung. Somit wird es nur einen Zug geben.
Basierend auf den Richtlinien im ersten Teil dieser Antwort können Sie, wenn Sie sich mit der Anzahl der Züge befassen, die generiert werden, wenn Sie CreditCardeinen By-Wert nehmen, zwei Konstruktorüberladungen definieren, von denen eine einen Wertverweis auf const( CreditCard const&) und eine nimmt eine rWertreferenz ( CreditCard&&).
Die Überlastungsauflösung wählt die erstere aus, wenn ein l-Wert übergeben wird (in diesem Fall wird eine Kopie ausgeführt), und die letztere, wenn ein r-Wert übergeben wird (in diesem Fall wird eine Bewegung ausgeführt).
Account(std::string number, float amount, CreditCard const& creditCard)
: number(number), amount(amount), creditCard(creditCard) // copy here
{ }
Account(std::string number, float amount, CreditCard&& creditCard)
: number(number), amount(amount), creditCard(std::move(creditCard)) // move here
{ }
Ihre Verwendung von std::forward<>wird normalerweise angezeigt, wenn Sie eine perfekte Weiterleitung erzielen möchten . In diesem Fall würde Ihr Konstruktor tatsächlich ein Konstruktor seine Vorlage , und mehr oder weniger würde wie folgt aussehen
template<typename C>
Account(std::string number, float amount, C&& creditCard)
: number(number), amount(amount), creditCard(std::forward<C>(creditCard)) { }
In gewisser Weise kombiniert dies beide Überladungen, die ich zuvor gezeigt habe, zu einer einzigen Funktion: CWird abgeleitet, CreditCard&falls Sie einen l-Wert übergeben, und aufgrund der Regeln zum Reduzieren der Referenz wird diese Funktion instanziiert:
Account(std::string number, float amount, CreditCard& creditCard) :
number(num), amount(amount), creditCard(std::forward<CreditCard&>(creditCard))
{ }
Dies führt zu einer Kopierkonstruktion von creditCard, wie Sie es wünschen. Wenn andererseits ein r-Wert übergeben wird, Cwird daraus abgeleitet CreditCard, und diese Funktion wird stattdessen instanziiert:
Account(std::string number, float amount, CreditCard&& creditCard) :
number(num), amount(amount), creditCard(std::forward<CreditCard>(creditCard))
{ }
Dies führt zu einer Verschiebungskonstruktion von creditCard, was Sie möchten (da der übergebene Wert ein r-Wert ist und wir also berechtigt sind, ihn zu verschieben).