LA QUESTION LA PLUS IMPORTANTE D'ABORD:
Existe-t-il des meilleures pratiques pour envoyer des paramètres en C ++ parce que je le trouve vraiment, disons, pas trivial
Si votre fonction a besoin de modifier l'objet d'origine passé, de sorte qu'après le retour de l'appel, les modifications apportées à cet objet soient visibles pour l'appelant, alors vous devez passer par la référence lvalue :
void foo(my_class& obj)
{
// Modify obj here...
}
Si votre fonction n'a pas besoin de modifier l'objet d'origine, et n'a pas besoin d'en créer une copie (en d'autres termes, il suffit d'observer son état), alors vous devez passer par référence lvalue àconst
:
void foo(my_class const& obj)
{
// Observe obj here
}
Cela vous permettra d'appeler la fonction à la fois avec des lvalues (les lvalues sont des objets avec une identité stable) et avec des rvalues (les rvalues sont, par exemple, des temporaires ou des objets dont vous êtes sur le point de vous déplacer suite à un appel std::move()
).
On pourrait également soutenir que pour les types fondamentaux ou les types pour lesquels la copie est rapide , tels que int
,, bool
ou char
, il n'est pas nécessaire de passer par référence si la fonction a simplement besoin d'observer la valeur, et le passage par valeur doit être privilégié . C'est correct si la sémantique de référence n'est pas nécessaire, mais que se passerait-il si la fonction voulait stocker un pointeur vers ce même objet d'entrée quelque part, de sorte que les futures lectures de ce pointeur voient les modifications de valeur qui ont été effectuées dans une autre partie du code? Dans ce cas, le passage par référence est la bonne solution.
Si votre fonction n'a pas besoin de modifier l'objet d'origine, mais doit stocker une copie de cet objet ( éventuellement pour renvoyer le résultat d'une transformation de l'entrée sans modifier l'entrée ), alors vous pouvez envisager de prendre par valeur :
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...
}
L'appel de la fonction ci-dessus entraînera toujours une copie lors du passage de lvalues, et un déplacement lors du passage de rvalues. Si votre fonction a besoin de stocker cet objet quelque part, vous pouvez effectuer un déplacement supplémentaire à partir de celui-ci (par exemple, dans le cas où il foo()
s'agit d' une fonction membre qui doit stocker la valeur dans un membre de données ).
Dans le cas où les déplacements sont coûteux pour les objets de type my_class
, alors vous pouvez envisager de surcharger foo()
et fournir une version pour lvalues (acceptant une référence lvalue à const
) et une version pour rvalues (acceptant une référence rvalue):
// 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...
}
Les fonctions ci-dessus sont si similaires, en fait, que vous pourriez en faire une seule fonction: foo()
pourrait devenir un modèle de fonction et vous pourriez utiliser un transfert parfait pour déterminer si un déplacement ou une copie de l'objet passé sera généré en interne:
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...
}
Vous voudrez peut-être en savoir plus sur cette conception en regardant cette conférence de Scott Meyers (faites attention au fait que le terme « références universelles » qu'il utilise n'est pas standard).
Une chose à garder à l'esprit est que std::forward
cela se terminera généralement par un mouvement pour les valeurs, donc même si cela semble relativement innocent, transmettre le même objet plusieurs fois peut être une source de problèmes - par exemple, passer deux fois du même objet! Veillez donc à ne pas mettre cela en boucle, et à ne pas transmettre le même argument plusieurs fois dans un appel de fonction:
template<typename C>
void foo(C&& obj)
{
bar(std::forward<C>(obj), std::forward<C>(obj)); // Dangerous!
}
Notez également que vous ne recourez normalement pas à la solution basée sur un modèle, sauf si vous avez une bonne raison, car cela rend votre code plus difficile à lire. Normalement, vous devez vous concentrer sur la clarté et la simplicité .
Ce ne sont que de simples directives, mais la plupart du temps, elles vous orienteront vers de bonnes décisions de conception.
CONCERNANT LE RESTE DE VOTRE POSTE:
Si je le réécris comme [...] il y aura 2 coups et aucune copie.
Ce n'est pas correct. Pour commencer, une référence rvalue ne peut pas se lier à une lvalue, donc cela ne sera compilé que lorsque vous passerez une rvalue de type CreditCard
à votre constructeur. Par exemple:
// 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));
Mais cela ne fonctionnera pas si vous essayez de faire ceci:
CreditCard cc("12345",2,2015,1001);
Account acc("asdasd",345, cc); // ERROR! cc is an lvalue
Car cc
is an lvalue et les références rvalue ne peuvent pas se lier à lvalues. De plus, lors de la liaison d'une référence à un objet, aucun déplacement n'est effectué : c'est juste une liaison de référence. Ainsi, il n'y aura qu'un seul mouvement.
Donc, sur la base des instructions fournies dans la première partie de cette réponse, si vous êtes préoccupé par le nombre de mouvements générés lorsque vous prenez une CreditCard
valeur par, vous pouvez définir deux surcharges de constructeur, l'une prenant une référence lvalue à const
( CreditCard const&
) et l'autre prenant une référence rvalue ( CreditCard&&
).
La résolution de surcharge sélectionnera le premier lors du passage d'une lvalue (dans ce cas, une copie sera effectuée) et le dernier lors du passage d'une rvalue (dans ce cas, un déplacement sera effectué).
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
{ }
Votre utilisation de std::forward<>
est normalement visible lorsque vous souhaitez obtenir une transmission parfaite . Dans ce cas, votre constructeur serait en fait un modèle de constructeur , et ressemblerait plus ou moins comme suit
template<typename C>
Account(std::string number, float amount, C&& creditCard)
: number(number), amount(amount), creditCard(std::forward<C>(creditCard)) { }
Dans un sens, cela combine les deux surcharges que j'ai montrées précédemment en une seule fonction: C
sera déduit au CreditCard&
cas où vous passeriez une lvalue, et en raison des règles de réduction de référence, cela provoquera l'instanciation de cette fonction:
Account(std::string number, float amount, CreditCard& creditCard) :
number(num), amount(amount), creditCard(std::forward<CreditCard&>(creditCard))
{ }
Cela entraînera une copie-construction de creditCard
, comme vous le souhaiteriez. D'autre part, lorsqu'une rvalue est passée, C
elle sera déduite comme étant CreditCard
, et cette fonction sera instanciée à la place:
Account(std::string number, float amount, CreditCard&& creditCard) :
number(num), amount(amount), creditCard(std::forward<CreditCard>(creditCard))
{ }
Cela entraînera une construction de déplacement de creditCard
, ce que vous voulez (car la valeur passée est une rvalue, et cela signifie que nous sommes autorisés à en sortir).