为了账号安全,请及时绑定邮箱和手机立即绑定

如何正确传递参数?

如何正确传递参数?

C++
倚天杖 2019-09-18 19:47:05
我是C ++初学者,但不是编程初学者。我正在努力学习C ++(c ++ 11),对我来说,最重要的是有点不清楚:传递参数。我考虑过这些简单的例子:一个包含其所有成员基本类型的类:CreditCard(std::string number, int expMonth, int expYear,int pin):number(number), expMonth(expMonth), expYear(expYear), pin(pin)具有成员基本类型+ 1复杂类型的类:Account(std::string number, float amount, CreditCard creditCard) : number(number), amount(amount), creditCard(creditCard)具有成员基本类型的类+具有某种复杂类型的1个集合: Client(std::string firstName, std::string lastName, std::vector<Account> accounts):firstName(firstName), lastName(lastName), accounts(accounts)当我创建一个帐户时,我这样做:    CreditCard cc("12345",2,2015,1001);    Account acc("asdasd",345, cc);显然,在这种情况下,信用卡将被复制两次。如果我重写该构造函数为Account(std::string number, float amount, CreditCard& creditCard)     : number(number)    , amount(amount)    , creditCard(creditCard)会有一份副本。如果我把它重写为Account(std::string number, float amount, CreditCard&& creditCard)     : number(number)    , amount(amount)    , creditCard(std::forward<CreditCard>(creditCard))将有2个动作,没有副本。我想有时您可能想要复制一些参数,有时您不希望在创建该对象时进行复制。我来自C#,用于引用,对我来说有点奇怪,我认为每个参数应该有2个重载,但我知道我错了。有没有关于如何在C ++中发送参数的最佳实践,因为我真的发现它,比方说,并非琐碎。你会如何处理我上面提到的例子?
查看完整描述

3 回答

?
POPMUISE

TA贡献1765条经验 获得超5个赞

最重要的问题第一:


有没有关于如何在C ++中发送参数的最佳实践,因为我真的发现它,比方说,并非琐碎


如果你的函数需要修改传递的原始对象,那么在调用返回后,调用者可以看到对该对象的修改,那么你应该通过左值引用传递:


void foo(my_class& obj)

{

    // Modify obj here...

}

如果你的函数不需要修改原始对象,并且不需要创建它的副本(换句话说,它只需要观察它的状态),那么你应该通过左值引用const传递给:


void foo(my_class const& obj)

{

    // Observe obj here

}

这将允许您使用左值(lvalues是具有稳定标识的对象)和rvalues(rvalues,例如临时值,或者您将要调用的对象移动的对象)来调用该函数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()并为左值(接受左值引用const)提供一个版本,为rvalues 提供一个版本(接受右值引用):


// 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通常最终会转向 rvalues,所以即使看起来相对无辜,多次转发同一个对象也可能是麻烦的来源 - 例如,从同一个对象移动两次!所以注意不要把它放在循环中,也不要在函数调用中多次转发同一个参数:


template<typename C>

void foo(C&& obj)

{

    bar(std::forward<C>(obj), std::forward<C>(obj)); // Dangerous!

}

另请注意,除非您有充分的理由,否则通常不会使用基于模板的解决方案,因为这会使您的代码难以阅读。通常,您应该注重清晰度和简洁性。


以上只是简单的指导原则,但大部分时间它们都会指向您做出良好的设计决策。


关于你的帖子:


如果我将其重写为[...],则会有2个动作而没有副本。


这是不正确的。首先,rvalue引用不能绑定到左值,因此只有在将rvalue类型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是左值和右值引用不能绑定到左值。此外,当绑定对象的引用时,不执行任何移动:它只是一个引用绑定。因此,只会有一个举动。


因此,根据本答案第一部分提供的指导原则,如果您关注采用CreditCardby值时生成的移动数,则可以定义两个构造函数重载,一个采用左值引用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,正如你所想。另一方面,当rvalue被传递时,C将被推断为CreditCard,并且将实例化该函数:


Account(std::string number, float amount, CreditCard&& creditCard) : 

number(num), amount(amount), creditCard(std::forward<CreditCard>(creditCard)) 

{ }

这将导致移动建设的creditCard,这是你想要的(因为正在传递的价值是一个右值,这意味着我们被授权从它移动)。


查看完整回答
反对 回复 2019-09-18
  • 3 回答
  • 0 关注
  • 394 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信