最重要的问题首先:
有没有关于如何在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
,也没有必要按引用传递如果函数只需要观察值,和路过的价值应该被看好。如果不需要引用语义,那是正确的;但是,如果函数想要在某个地方存储指向该相同输入对象的指针,以便将来通过该指针进行读取,将会看到在该指针的其他部分执行的值修改,那是正确的。码?在这种情况下,通过引用传递是正确的解决方案。
如果您的函数不需要修改原始对象,但是需要存储该对象的副本(可能返回输入的转换结果而不更改输入),那么您可以考虑按值取值:
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...
}
调用上述函数将在传递左值时始终导致一个副本,而在传递左值时将导致一个动作。如果你的函数需要存储这个对象的地方,你可以执行额外的举措从它(例如,在案件foo()
是一个成员函数,需要存储在数据成员的值)。
如果对于类型的对象而言移动昂贵my_class
,那么您可以考虑重载,foo()
并为lvalues提供一个版本(接受对的lvalue引用const
),为rvalues 提供一个版本(接受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...
}
上面的函数是如此相似,实际上,您可以从中创建一个函数:foo()
可以成为函数模板,并且可以使用完美的转发来确定是内部生成传递的对象的移动还是复制:
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...
}
您可能想通过观看Scott Meyers的演讲来了解有关此设计的更多信息(请注意,他使用的术语“ 通用参考 ”是非标准的事实)。
要记住的一件事是,std::forward
通常最终将导致右值的移动,因此,即使看上去相对无害,多次转发同一对象也可能会带来麻烦-例如,从同一对象移动两次!因此请注意不要将其置于循环中,并且不要在函数调用中多次转发相同的参数:
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
,这是你想要的(因为正在传递的价值是一个右值,这意味着我们被授权从它移动)。