如何正确传递参数? [英] How to pass parameters correctly?

查看:209
本文介绍了如何正确传递参数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

正如我在其他问题中所说,我是一个C ++初学者,但不是一个编程初学者。
我试图学习C ++(c ++ 11),它对我来说不清楚最重要的事情:传递参数。



我认为这些简单示例:




  • 具有其所有成员原始类型的类:

    CreditCard(std :: string number,int expMonth,int expYear,int pin):number(number),expMonth(expMonth),expYear(expYear),pin(pin)


  • 具有成员原始类型+ 1复杂类型的类:

    帐户(std :: string number,float amount,CreditCard creditCard):number(number),amount(amount),creditCard(creditCard)


  • +一些复杂类型的集合:
    Client(std :: string firstName,std :: string lastName,std :: vector< Account> accounts):firstName(firstName),lastName ),accounts(accounts)




创建帐户时,

  CreditCard cc(12345,2,2015,1001); 
帐户acc(asdasd,345,cc);

显然,在这种情况下信用卡将被复制两次。
如果我将该构造函数重写为

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

会有一个副本。
如果我将其重写为

 帐户(std :: string number,float amount,CreditCard& 
:number(number)
,amount(amount)
,creditCard(std :: forward< CreditCard>(creditCard))


我认为有时你可能想要复制一些参数,有时你不想在创建该对象时复制。

我来自C#,用于引用,对我来说有点奇怪,我认为每个参数都应该有2个重载,但是我知道我错了。

有没有任何最佳实践,如何发送参数在C ++,因为我真的发现它,让我们说,不是微不足道。你将如何处理我上面提出的例子?

谢谢你们读这个。我期待着您的回复,以便我可以学习一些新的东西。

解决方案

最重要的问题第一: / strong>


有没有任何最佳实践,如何发送参数在C ++中,因为我真的发现它, p>

如果您的函数需要修改正在传递的原始对象,以便在调用返回后,对象将对调用者可见,那么您应该通过左值引用

  void foo my_class& obj)
{
//在此修改obj ...
}

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

  void foo(my_class const& obj)
{
//观察obj here
}

这将允许您使用左值(左值是具有稳定身份的对象)和右值(右值,例如临时值)或要从中移动的对象调用函数调用 std :: move())。



复制快速的类型,例如 int bool char ,如果函数只需要观察值,则不需要通过引用传递,而传递值应该优先。如果不需要引用语义,这是正确的,但如果函数想要在某处存储一个指向同一个输入对象的指针,那么以后的读取将通过该指针看到已修改的值在代码的其他部分执行?在这种情况下,通过引用传递是正确的解决方案。



如果您的函数不需要修改原始对象,对象可能返回输入变换的结果,而不更改输入),那么您可以考虑按值

  void foo(my_class obj)//一个副本或一个移动到这里,但不工作
//原始对象...
{
//处理obj ...

//如果结果必须存储在某个地方,可能从obj移动...
}

调用上面的函数在传递左值时总是产生一个副本,而在传递右值时会一次移动一次。如果你的函数需要把这个对象存储在某个地方,你可以从它执行一个额外的 move (例如,在 foo() a href =http://stackoverflow.com/questions/14197526/should-all-most-setter-functions-in-c11-be-written-as-function-templates-accep>需要存储的成员函数数据成员中的值)。



对于 my_class 类型的对象,如果移动很贵重载 foo()并为lvalues提供一个版本(接受对 const 的左值引用)接受一个右值引用):

  // lvalues的重载
void foo(my_class const& obj)// No复制,没有移动(只是引用绑定)
{
my_class copyOfObj = obj; //复制!
//使用copyOfObj ...
}

// rvalues的重载
void foo(my_class&& obj)//没有副本(只是引用绑定)
{
my_class copyOfObj = std :: move(obj); //移动!
//注意,调用std :: move()是
//这里需要,因为obj是一个
// * lvalue *,即使它的类型是
/ /rvalue reference to my_class。
//使用copyOfObj ...
}

上述函数所以类似,事实上,你可以使一个单一的功能: foo()可以成为一个函数模板,你可以使用完美转发以确定正在传递的对象的移动或副本是否将在内部生成:

  template< typename C> 
void foo(C&& obj)//没有副本,没有移动(只是引用绑定)
// ^^^
//注意,这不总是一个右值引用!这将magically
//解析成my_class&如果通过了一个左值,并且my_class&&如果
// rvalue被传递
{
my_class copyOfObj = std :: forward< C>(obj); // Copy if lvalue,move if rvalue
//使用copyOfObj ...
}

您可以通过观看(只是要注意,他使用的术语通用引用是非标准的)。

需要记住的一件事是, std :: forward 通常会在 move 对于右值,所以即使它看起来相对无辜,多次转发相同的对象可能是一个麻烦的来源 - 例如,从同一对象移动两次!所以小心不要把它放在一个循环中,不要在函数调用中多次转发同一个参数:

  template< ;类型名C> 
void foo(C&& obj)
{
bar(std :: forward< C>(obj),std :: forward< C //危险!
}

另请注意,您通常不会诉诸于基于模板的解决方案,除非你有一个很好的理由,因为它使你的代码更难读。 通常,您应该专注于清晰度和简单



以上只是简单的指南,但大多数时候他们会指向您设计决策。






关于邮件的后续处理


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


这不正确。首先,右值引用不能绑定到左值,所以只有当你传递类型 CreditCard 到您的构造函数的右值时,才会编译。例如:

  //这里你传递一个临时的(OK!临时值为rvalues)
帐户acc asdasd,345,CreditCard(12345,2,2015,1001));

CreditCard cc(12345,2,2015,1001);
//这里你传递的结果std :: move(OK!这也是一个右值)
帐户acc(asdasd,345,std :: move(cc));

但如果您尝试执行此操作将无法正常工作:

  CreditCard cc(12345,2,2015,1001); 
帐户acc(asdasd,345,cc); //错误! cc是一个左值

因为 cc lvalue和rvalue引用不能绑定到lvalue。此外,绑定对象的引用时,不执行移动:它只是引用绑定。因此,只会有一个移动。






这个答案的第一部分,如果你关心当你通过值取一个 CreditCard 生成的移动数量,你可以定义两个构造函数重载,一个采用左值引用到 const CreditCard const& )和一个采用右值引用( CreditCard&



当传递一个左值时,过载分辨率将选择前者(在这种情况下,将执行一个副本),后者在传递时右值(在这种情况下,将执行一次移动)。

 帐户(std :: string number,float amount,CreditCard const& creditCard)
:number amount(amount),creditCard(creditCard)// copy here
{}

帐户(std :: string number,float amount,CreditCard&& creditCard)
:number (number),amount(amount),creditCard(std :: move(creditCard))//移动到这里
{}






您在使用 std :: forward<> 完美转发。在这种情况下,你的构造函数实际上是一个构造函数模板,并且看起来或多或少如下:

  template< typename C> 
帐户(std :: string number,float amount,C&&& amp; amp; }

在某种意义上,这将我之前展示的重载组合到一个单独的函数中: code> C 将被推导为 CreditCard& ,以防您传递左值,并且由于参考折叠规则将使此函数实例化:

 帐户(std :: string number,float amount,CreditCard& creditCard):
number(num),amount(amount),creditCard(std :: forward< CreditCard&>(creditCard))
{}

这将导致 creditCard 复制建立,如您所愿。另一方面,当一个右值被传递, C 将被推导为 CreditCard ,这个函数将实例化:

 帐户(std :: string number,float amount,CreditCard&& creditCard):
number (num),amount(amount),creditCard(std :: forward< CreditCard>(creditCard))
{}

这将导致 creditCard 移动构造,这是你想要的(因为传递的值是rvalue,这意味着我们有权从中移除)。


As I said in my other questions, I am a C++ beginner but not a programming beginner. I'm trying to learn C++(c++11) and it's kinda unclear for me the most important thing: passing parameters.

I considered these simple examples:

  • A class that has all its members primitive types:
    CreditCard(std::string number, int expMonth, int expYear,int pin):number(number), expMonth(expMonth), expYear(expYear), pin(pin)

  • A class that has as members primitive types + 1 complex type:
    Account(std::string number, float amount, CreditCard creditCard) : number(number), amount(amount), creditCard(creditCard)

  • A class that has as members primitive types + 1 collection of some complex type: Client(std::string firstName, std::string lastName, std::vector<Account> accounts):firstName(firstName), lastName(lastName), accounts(accounts)

When I create an account, I do this:

    CreditCard cc("12345",2,2015,1001);
    Account acc("asdasd",345, cc);

Obviously the credit card will be copied twice in this scenario. If I rewrite that constructor as

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

there will be one copy. If I rewrite it as

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

there will be 2 moves and no copy.

I think sometimes you may want to copy some parameter, sometimes you don't want to copy when you create that object.
I come from C# and, being used to references, it's a bit strange to me and I think there should be 2 overloads for each parameter but I know I am wrong.
Are there any best practices of how to send parameters in C++ because I really find it, let's say, not trivial. How would you handle my examples presented above?
Thank you guys for reading this. I am looking forward to your replies so I can learn some new stuff.

解决方案

THE MOST IMPORTANT QUESTION FIRST:

Are there any best practices of how to send parameters in C++ because I really find it, let's say, not trivial

If your function needs to modify the original object being passed, so that after the call returns, modifications to that object will be visible to the caller, then you should pass by lvalue reference:

void foo(my_class& obj)
{
    // Modify obj here...
}

If your function does not need to modify the original object, and does not need to create a copy of it (in other words, it only needs to observe its state), then you should pass by lvalue reference to const:

void foo(my_class const& obj)
{
    // Observe obj here
}

This will allow you to call the function both with lvalues (lvalues are objects with a stable identity) and with rvalues (rvalues are, for instance temporaries, or objects you're about to move from as the result of calling std::move()).

One could also argue that for fundamental types or types for which copying is fast, such as int, bool, or char, there is no need to pass by reference if the function simply needs to observe the value, and passing by value should be favored. That is correct if reference semantics is not needed, but what if the function wanted to store a pointer to that very same input object somewhere, so that future reads through that pointer will see the value modifications that have been performed in some other part of the code? In this case, passing by reference is the correct solution.

If your function does not need to modify the original object, but needs to store a copy of that object (possibly to return the result of a transformation of the input without altering the input), then you could consider taking by value:

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...
}

Invoking the above function will always result in one copy when passing lvalues, and in one moves when passing rvalues. If your function needs to store this object somewhere, you could perform an additional move from it (for instance, in the case foo() is a member function that needs to store the value in a data member).

In case moves are expensive for objects of type my_class, then you may consider overloading foo() and provide one version for lvalues (accepting an lvalue reference to const) and one version for rvalues (accepting an rvalue reference):

// 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...
}

The above functions are so similar, in fact, that you could make one single function out of it: foo() could become a function template and you could use perfect forwarding for determining whether a move or a copy of the object being passed will be internally generated:

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...
}

You may want to learn more about this design by watching this talk by Scott Meyers (just mind the fact that the term "Universal References" that he is using is non-standard).

One thing to keep in mind is that std::forward will usually end up in a move for rvalues, so even though it looks relatively innocent, forwarding the same object multiple times may be a source of troubles - for instance, moving from the same object twice! So be careful not to put this in a loop, and not to forward the same argument multiple times in a function call:

template<typename C>
void foo(C&& obj)
{
    bar(std::forward<C>(obj), std::forward<C>(obj)); // Dangerous!
}

Also notice, that you normally do not resort to the template-based solution unless you have a good reason for it, as it makes your code harder to read. Normally, you should focus on clarity and simplicity.

The above are just simple guidelines, but most of the time they will point you towards good design decisions.


CONCERNING THE REST OF YOUR POST:

If i rewrite it as [...] there will be 2 moves and no copy.

This is not correct. To begin with, an rvalue reference cannot bind to an lvalue, so this will only compile when you are passing an rvalue of type CreditCard to your constructor. For instance:

// 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));

But it won't work if you try to do this:

CreditCard cc("12345",2,2015,1001);
Account acc("asdasd",345, cc); // ERROR! cc is an lvalue

Because cc is an lvalue and rvalue references cannot bind to lvalues. Moreover, when binding a reference to an object, no move is performed: it's just a reference binding. Thus, there will only be one move.


So based on the guidelines provided in the first part of this answer, if you are concerned with the number of moves being generated when you take a CreditCard by value, you could define two constructor overloads, one taking an lvalue reference to const (CreditCard const&) and one taking an rvalue reference (CreditCard&&).

Overload resolution will select the former when passing an lvalue (in this case, one copy will be performed) and the latter when passing an rvalue (in this case, one move will be performed).

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
{ }


Your usage of std::forward<> is normally seen when you want to achieve perfect forwarding. In that case, your constructor would actually be a constructor template, and would look more or less as follows

template<typename C>
Account(std::string number, float amount, C&& creditCard) 
: number(number), amount(amount), creditCard(std::forward<C>(creditCard)) { }

In a sense, this combines both the overloads I've shown previously into one single function: C will be deduced to be CreditCard& in case you are passing an lvalue, and due to the reference collapsing rules, it will cause this function to be instantiated:

Account(std::string number, float amount, CreditCard& creditCard) : 
number(num), amount(amount), creditCard(std::forward<CreditCard&>(creditCard)) 
{ }

This will cause a copy-construction of creditCard, as you would wish. On the other hand, when an rvalue is passed, C will be deduced to be CreditCard, and this function will be instantiated instead:

Account(std::string number, float amount, CreditCard&& creditCard) : 
number(num), amount(amount), creditCard(std::forward<CreditCard>(creditCard)) 
{ }

This will cause a move-construction of creditCard, which is what you want (because the value being passed is an rvalue, and that means we are authorized to move from it).

这篇关于如何正确传递参数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆