亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

如何正確傳遞參數?

如何正確傳遞參數?

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,也沒有必要按引用傳遞如果函數只需要觀察值,和路過的價值應該被看好。如果不需要引用語義,這是正確的,但是如果函數想要將指針存儲到某個相同的輸入對象,那么將來通過該指針讀取將會看到已在其他部分執行的值修改。碼?在這種情況下,通過引用傳遞是正確的解決方案。


如果你的函數不需要修改原始對象,但需要存儲該對象的副本(可能返回輸入轉換的結果而不改變輸入),那么你可以考慮按值獲?。?/p>


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 關注
  • 443 瀏覽

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號