最重要的问题首先:
有没有关于如何在 C++ 中发送参数的最佳实践,因为我真的找到了,比如说,不是微不足道的
如果您的函数需要修改传递的原始对象,以便调用返回后,调用者可以看到对该对象的修改,那么您应该通过左值引用传递:
void foo(my_class& obj)
{
// Modify obj here...
}
如果你的函数不需要修改原始对象,也不需要创建它的副本(换句话说,它只需要观察它的状态),那么你应该通过通过对const的左值引用:
void foo(my_class const& obj)
{
// Observe obj here
}
这将允许您使用左值(左值是具有稳定标识的对象)和右值(右值是,例如 temporaries,或者您将要从中移动的对象来调用该函数作为调用std::move()的结果。
也有人会争辩说,对于基本类型或复制速度很快的类型,例如int、bool 或char,如果满足以下条件,则无需通过引用传递函数只需要观察值,应该优先考虑传值。如果 reference semantics 不需要,那是正确的,但如果函数想在某处存储指向同一个输入对象的指针怎么办,以便将来通过该指针读取将看到已修改的值在代码的其他部分执行?在这种情况下,通过引用传递是正确的解决方案。
如果您的函数不需要修改原始对象,但需要存储该对象的副本(可能返回输入转换的结果而不改变输入),那么你可以考虑按价值取值:
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...
}
调用上述函数在传递左值时总是会产生一份副本,而在传递右值时总是会产生一份移动。如果您的函数需要将此对象存储在某处,您可以从中执行一个额外的move(例如,在foo() 是a member function that needs to store the value in a data member 的情况下)。
如果 my_class 类型的对象的移动成本很高,那么您可以考虑重载 foo() 并为左值提供一个版本(接受对 const 的左值引用)和一个版本对于右值(接受右值引用):
// 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...
}
上面的函数非常相似,事实上,你可以把它做成一个函数:foo()可以变成一个函数模板,你可以使用完美转发 strong> 用于确定是否将在内部生成正在传递的对象的移动或副本:
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...
}
您可能想通过观看 this talk by Scott Meyers 了解有关此设计的更多信息(请注意,他使用的术语“通用参考”是非标准的)。
要记住的一点是,std::forward 通常会以 move 结束右值,因此即使它看起来相对无害,多次转发同一个对象也可能是麻烦 - 例如,从同一个物体移动两次!所以注意不要把它放在一个循环中,也不要在函数调用中多次转发同一个参数:
template<typename C>
void foo(C&& obj)
{
bar(std::forward<C>(obj), std::forward<C>(obj)); // Dangerous!
}
另外请注意,除非您有充分的理由,否则您通常不会求助于基于模板的解决方案,因为它会使您的代码更难阅读。 通常,您应该专注于清晰和简单。
以上只是简单的指导方针,但大多数时候它们会为您指明正确的设计决策。
关于您帖子的其余部分:
如果我将它改写为 [...] 将有 2 个动作并且没有副本。
这是不正确的。首先,右值引用不能绑定到左值,因此只有在将CreditCard 类型的右值传递给构造函数时才会编译。例如:
// 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));
但如果您尝试这样做,它将不起作用:
CreditCard cc("12345",2,2015,1001);
Account acc("asdasd",345, cc); // ERROR! cc is an lvalue
因为cc 是一个左值,而右值引用不能绑定到左值。此外,将引用绑定到对象时,不会执行任何移动:它只是一个引用绑定。因此,只会有一个动作。
因此,根据此答案第一部分中提供的指南,如果您担心按值获取 CreditCard 时生成的移动次数,您可以定义两个构造函数重载,一个采用左值引用到 const (CreditCard const&) 和一个采用右值引用 (CreditCard&&)。
重载解析会在传递左值时选择前者(在这种情况下,将执行一次复制),在传递右值时会选择后者(在这种情况下,将执行一次移动)。
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
{ }
您对std::forward<> 的使用通常在您想要实现完美转发时看到。在这种情况下,你的构造函数实际上是一个构造函数模板,看起来或多或少如下
template<typename C>
Account(std::string number, float amount, C&& creditCard)
: number(number), amount(amount), creditCard(std::forward<C>(creditCard)) { }
从某种意义上说,这将我之前展示的两个重载组合到一个函数中:C 将被推断为CreditCard&,以防你传递一个左值,并且由于引用折叠规则,它将导致该函数被实例化:
Account(std::string number, float amount, CreditCard& creditCard) :
number(num), amount(amount), creditCard(std::forward<CreditCard&>(creditCard))
{ }
如您所愿,这将导致creditCard 的复制构造。另一方面,当传递一个右值时,C 会被推导出为CreditCard,而这个函数会被实例化:
Account(std::string number, float amount, CreditCard&& creditCard) :
number(num), amount(amount), creditCard(std::forward<CreditCard>(creditCard))
{ }
这将导致creditCard 的move-construction,这是您想要的(因为传递的值是一个右值,这意味着我们有权从它移动)。