จะส่งพารามิเตอร์อย่างไรให้ถูกต้อง?


108

ฉันเป็นผู้เริ่มต้นใช้งาน 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 ++ หรือไม่เพราะฉันพบว่ามันไม่ใช่เรื่องเล็กน้อย คุณจะจัดการกับตัวอย่างของฉันที่นำเสนอข้างต้นอย่างไร


9
เมตา: ฉันไม่อยากเชื่อเลยว่ามีคนถามคำถาม C ++ ที่ดี +1.

23
FYI std::stringเป็นคลาสเช่นเดียวกับCreditCardไม่ใช่ประเภทดั้งเดิม
chris

7
เนื่องจากความสับสนของสตริงแม้ว่าจะไม่เกี่ยวข้องกันคุณควรทราบว่าสตริงลิเทอรัล"abc"ไม่ใช่ประเภทstd::stringไม่ใช่ประเภทchar */const char *แต่เป็นประเภทconst char[N](ที่มี N = 4 ในกรณีนี้เนื่องจากอักขระสามตัวและค่าว่าง) นั่นเป็นความเข้าใจผิดทั่วไปที่ดีที่จะหลีกเลี่ยง
chris

10
@chuex: Jon Skeet เป็นคำถาม C # ทั้งหมดซึ่งไม่ค่อยมี C ++
Steve Jessop

คำตอบ:


158

คำถามที่สำคัญที่สุดอันดับแรก:

มีแนวทางปฏิบัติที่ดีที่สุดเกี่ยวกับวิธีการส่งพารามิเตอร์ใน C ++ หรือไม่เพราะฉันพบว่ามันไม่ใช่เรื่องเล็กน้อย

หากฟังก์ชันของคุณต้องการแก้ไขออบเจ็กต์ดั้งเดิมที่กำลังส่งผ่านดังนั้นหลังจากการโทรกลับมาแล้วผู้โทรจะมองเห็นการแก้ไขวัตถุนั้นคุณควรส่งผ่านการอ้างอิง lvalue :

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

หากฟังก์ชันของคุณไม่จำเป็นต้องแก้ไขออบเจ็กต์ดั้งเดิมและไม่จำเป็นต้องสร้างสำเนาของมัน (กล่าวอีกนัยหนึ่งก็คือต้องสังเกตสถานะของมันเท่านั้น) คุณควรผ่านการอ้างอิง lvalue ไปที่const :

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

นี้จะช่วยให้คุณสามารถเรียกใช้ฟังก์ชันทั้งที่มี lvalues (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...
}

การเรียกใช้ฟังก์ชันข้างต้นจะส่งผลให้ได้สำเนาเดียวเสมอเมื่อส่งผ่านค่า lvalues ​​และในการย้ายครั้งเดียวเมื่อส่งค่า rvalues หากฟังก์ชันของคุณต้องการจัดเก็บอ็อบเจ็กต์นี้ไว้ที่ใดที่หนึ่งคุณสามารถดำเนินการย้ายเพิ่มเติมจากอ็อบเจ็กต์ได้ (เช่นในกรณี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 (โปรดทราบว่าคำว่า " Universal References " ที่เขาใช้นั้นไม่เป็นมาตรฐาน)

สิ่งหนึ่งที่ควรทราบก็คือstd::forwardมักจะจบลงด้วยการย้ายค่า rvalues ​​ดังนั้นแม้ว่ามันจะดูไร้เดียงสา แต่การส่งต่อวัตถุเดียวกันหลาย ๆ ครั้งอาจเป็นสาเหตุของปัญหาตัวอย่างเช่นการย้ายจากวัตถุเดียวกันสองครั้ง! ดังนั้นระวังอย่าใส่สิ่งนี้ในการวนซ้ำและอย่าส่งต่ออาร์กิวเมนต์เดียวกันหลาย ๆ ครั้งในการเรียกใช้ฟังก์ชัน:

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

โปรดสังเกตด้วยว่าโดยปกติคุณจะไม่หันไปใช้โซลูชันที่ใช้เทมเพลตเว้นแต่คุณจะมีเหตุผลที่ดีเนื่องจากจะทำให้โค้ดของคุณอ่านยากขึ้น ปกติคุณควรมุ่งเน้นความคมชัดและความเรียบง่าย

ข้างต้นเป็นเพียงแนวทางง่ายๆ แต่ส่วนใหญ่แล้วจะชี้ให้คุณตัดสินใจออกแบบที่ดี


คำนึงถึงส่วนที่เหลือของโพสต์ของคุณ:

ถ้าฉันเขียนใหม่เป็น [... ] จะมี 2 ท่าและไม่มีการคัดลอก

สิ่งนี้ไม่ถูกต้อง เริ่มต้นด้วยการอ้างอิง rvalue ไม่สามารถผูกกับ lvalue ได้ดังนั้นสิ่งนี้จะคอมไพล์ก็ต่อเมื่อคุณส่งค่า rvalue ของ type 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เป็นการอ้างอิง lvalue และ rvalue ไม่สามารถผูกกับ lvalues ​​ได้ ยิ่งไปกว่านั้นเมื่อเชื่อมโยงการอ้างอิงกับวัตถุจะไม่มีการย้ายใด ๆแต่เป็นเพียงการเชื่อมโยงข้อมูลอ้างอิง ดังนั้นจะมีเพียงหนึ่งย้าย


ดังนั้นตามแนวทางที่ให้ไว้ในส่วนแรกของคำตอบนี้หากคุณกังวลเกี่ยวกับจำนวนการเคลื่อนไหวที่สร้างขึ้นเมื่อคุณใช้ a CreditCardby value คุณสามารถกำหนด constructor overloads ได้สองตัวโดยตัวหนึ่งใช้การอ้างอิง lvalue ไปที่const( CreditCard const&) และหนึ่งการใช้ การอ้างอิง rvalue ( CreditCard&&)

ความละเอียดเกินจะเลือกอดีตเมื่อผ่าน lvalue (ในกรณีนี้จะดำเนินการหนึ่งสำเนา) และหลังเมื่อผ่านค่า rvalue (ในกรณีนี้จะทำการย้ายครั้งเดียว)

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&ในกรณีที่คุณส่งค่า lvalue และเนื่องจากกฎการยุบการอ้างอิงจะทำให้ฟังก์ชันนี้ถูกสร้างอินสแตนซ์:

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

นี้จะทำให้เกิดการคัดลอกก่อสร้างของcreditCardที่คุณจะต้องการ ในทางกลับกันเมื่อค่า r ถูกส่งผ่านCจะถูกอนุมานให้เป็นCreditCardและฟังก์ชันนี้จะถูกสร้างอินสแตนซ์แทน:

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

นี้จะทำให้เกิดการย้ายการก่อสร้างของcreditCardซึ่งเป็นสิ่งที่คุณต้องการ (เพราะค่าที่ถูกส่งเป็น rvalue และนั่นหมายความว่าเราจะได้รับอนุญาตให้ย้ายจากมัน)


ผิดหรือไม่ที่จะบอกว่าสำหรับประเภทพารามิเตอร์ที่ไม่ใช่แบบดั้งเดิมสามารถใช้เวอร์ชันเทมเพลตพร้อมส่งต่อได้เสมอ
Jack Willson

1
@JackWillson: ฉันจะบอกว่าคุณควรใช้เวอร์ชันเทมเพลตเฉพาะเมื่อคุณมีความกังวลเกี่ยวกับประสิทธิภาพของการเคลื่อนไหวเท่านั้น ดูคำถามของฉันซึ่งมีคำตอบที่ดีสำหรับข้อมูลเพิ่มเติม constโดยทั่วไปถ้าคุณไม่ได้มีการทำสำเนาและจะต้องสังเกตใช้เวลาโดนโทษไป หากคุณต้องการแก้ไขออบเจ็กต์ดั้งเดิมให้อ้างถึง non-`const หากคุณต้องการทำสำเนาและการเคลื่อนย้ายมีราคาถูกให้ใช้ตามมูลค่าแล้วย้าย
Andy Prowl

2
ฉันคิดว่าไม่จำเป็นต้องย้ายในตัวอย่างโค้ดที่สาม คุณสามารถใช้objแทนการทำสำเนาในเครื่องเพื่อย้ายเข้าไปได้ใช่ไหม
juanchopanza

3
@AndyProwl: คุณได้ +1 ชั่วโมงที่แล้ว แต่อ่านคำตอบ (ยอดเยี่ยม) ของคุณอีกครั้งฉันต้องการชี้ให้เห็นสองสิ่ง: a) by-value ไม่ได้สร้างสำเนาเสมอไป (หากไม่ได้ย้าย) วัตถุสามารถสร้างขึ้นในสถานที่บนไซต์ผู้โทรโดยเฉพาะอย่างยิ่งกับ RVO / NRVO ซึ่งทำงานได้บ่อยกว่าที่คิดไว้ก่อน ข) กรุณาชี้ให้เห็นว่าในตัวอย่างที่ผ่านมาstd::forwardอาจจะเรียกว่าเพียงครั้งเดียว ฉันเคยเห็นคนใส่มันเข้าไปในลูป ฯลฯ และเนื่องจากผู้เริ่มต้นจำนวนมากจะเห็นคำตอบนี้ IMHO ควรมีคำเตือน "คำเตือน!" ที่อ้วนเพื่อช่วยหลีกเลี่ยงกับดักนี้
Daniel Frey

1
@SteveJessop: และนอกเหนือจากนั้นยังมีปัญหาทางเทคนิค (ไม่จำเป็นต้องเป็นปัญหา) กับฟังก์ชั่นการส่งต่อ ( โดยเฉพาะตัวสร้างที่ใช้อาร์กิวเมนต์เดียว ) ส่วนใหญ่เป็นความจริงที่ว่าพวกเขายอมรับข้อโต้แย้งประเภทใดก็ได้และอาจเอาชนะstd::is_constructible<>ลักษณะประเภทเว้นแต่ว่าจะมี SFINAE อย่างถูกต้อง จำกัด - ซึ่งอาจไม่สำคัญสำหรับบางคน
Andy Prowl

11

ก่อนอื่นให้ฉันแก้ไขรายละเอียดบางอย่าง เมื่อคุณพูดสิ่งต่อไปนี้:

จะมีการเคลื่อนไหว 2 ครั้งและไม่มีการคัดลอก

นั่นเป็นเท็จ การผูกกับการอ้างอิง rvalue ไม่ใช่การย้าย มีอย่างเดียวคือย้าย

นอกจากนี้เนื่องจากCreditCardไม่ใช่พารามิเตอร์เทมเพลตstd::forward<CreditCard>(creditCard)จึงเป็นเพียงวิธีการพูดstd::move(creditCard)โดยละเอียด

ตอนนี้ ...

หากประเภทของคุณมีการเคลื่อนไหวที่ "ถูก" คุณอาจต้องการเพียงแค่ทำให้ชีวิตของคุณง่ายขึ้นและใช้ทุกอย่างด้วยคุณค่าและ " std::moveพร้อม"

Account(std::string number, float amount, CreditCard creditCard)
: number(std::move(number),
  amount(amount),
  creditCard(std::move(creditCard)) {}

วิธีนี้จะทำให้คุณได้รับสองการเคลื่อนไหวเมื่อมันสามารถให้ผลเพียงครั้งเดียว แต่ถ้าการเคลื่อนไหวมีราคาถูกก็อาจจะยอมรับได้

ในขณะที่เราอยู่ในเรื่อง "การเคลื่อนไหวราคาถูก" นี้ฉันควรเตือนคุณว่าstd::stringมักใช้กับสิ่งที่เรียกว่าการเพิ่มประสิทธิภาพสตริงขนาดเล็กดังนั้นการเคลื่อนไหวของมันอาจไม่ถูกเท่ากับการคัดลอกคำแนะนำบางอย่าง ตามปกติสำหรับปัญหาการเพิ่มประสิทธิภาพไม่ว่าจะสำคัญหรือไม่เป็นเรื่องที่ต้องถามผู้สร้างโปรไฟล์ของคุณไม่ใช่ฉัน

จะทำอย่างไรถ้าคุณไม่ต้องการรับการเคลื่อนไหวพิเศษเหล่านั้น? บางทีอาจพิสูจน์ได้ว่าแพงเกินไปหรือแย่กว่านั้นบางทีประเภทไม่สามารถเคลื่อนย้ายได้จริงและคุณอาจต้องซื้อสำเนาเพิ่มเติม

ถ้ามีเพียงหนึ่งพารามิเตอร์ที่มีปัญหาคุณสามารถให้ overloads สองด้วยและT const& T&&ซึ่งจะผูกข้อมูลอ้างอิงตลอดเวลาจนกว่าจะเริ่มต้นสมาชิกจริงซึ่งจะมีการคัดลอกหรือย้ายเกิดขึ้น

อย่างไรก็ตามหากคุณมีพารามิเตอร์มากกว่าหนึ่งพารามิเตอร์สิ่งนี้จะนำไปสู่การระเบิดแบบเอ็กซ์โพเนนเชียลในจำนวนโอเวอร์โหลด

นี่เป็นปัญหาที่สามารถแก้ไขได้ด้วยการส่งต่อที่สมบูรณ์แบบ นั่นหมายความว่าคุณเขียนเทมเพลตแทนและใช้std::forwardเพื่อดำเนินการตามหมวดหมู่ค่าของอาร์กิวเมนต์ไปยังปลายทางสุดท้ายในฐานะสมาชิก

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

มีปัญหากับเวอร์ชันเทมเพลต: ผู้ใช้ไม่สามารถเขียนได้Account("",0,{brace, initialisation})อีกต่อไป
ipc

@ipc อ่าจริงป่าว นั่นเป็นเรื่องที่น่ารำคาญจริงๆและฉันไม่คิดว่าจะมีวิธีแก้ปัญหาที่ปรับขนาดได้ง่าย
R. Martinho Fernandes

6

ครั้งแรกของทั้งหมดค่อนข้างเป็นประเภทระดับหนักเช่นเดียวกับstd::string std::vectorไม่ใช่เรื่องดั้งเดิมอย่างแน่นอน

หากคุณนำประเภทที่เคลื่อนย้ายได้ขนาดใหญ่ตามค่ามาเป็นตัวสร้างฉันจะstd::moveให้มันเป็นสมาชิก:

CreditCard(std::string number, float amount, CreditCard creditCard)
  : number(std::move(number)), amount(amount), creditCard(std::move(creditCard))
{ }

นี่คือวิธีที่ฉันอยากจะแนะนำให้ใช้ตัวสร้าง มันทำให้สมาชิกnumberและcreditCardถูกสร้างขึ้นแทนที่จะสร้างสำเนา เมื่อคุณใช้ตัวสร้างนี้จะมีหนึ่งสำเนา (หรือย้ายถ้าชั่วคราว) เมื่อวัตถุถูกส่งไปยังตัวสร้างและจากนั้นย้ายหนึ่งครั้งเมื่อเริ่มต้นสมาชิก

ลองพิจารณาตัวสร้างนี้:

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

คุณถูกต้องสิ่งนี้จะเกี่ยวข้องกับสำเนาหนึ่งชุดcreditCardเนื่องจากถูกส่งไปยังผู้สร้างก่อนโดยการอ้างอิง แต่ตอนนี้คุณไม่สามารถส่งผ่านconstวัตถุไปยังตัวสร้างได้ (เนื่องจากการอ้างอิงไม่ใช่const) และคุณไม่สามารถส่งผ่านวัตถุชั่วคราวได้ ตัวอย่างเช่นคุณไม่สามารถทำได้:

Account account("something", 10.0f, CreditCard("12345",2,2015,1001));

ลองพิจารณา:

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

ที่นี่คุณจะได้แสดงให้เห็นถึงความเข้าใจผิดของการอ้างอิง rvalue std::forwardและ คุณควรใช้จริงๆก็ต่อstd::forwardเมื่อวัตถุที่คุณส่งต่อถูกประกาศว่าเป็นประเภทอนุมานT&&บางส่วนเท่านั้น ที่นี่ไม่ได้อนุมาน (ฉันสมมติ) ดังนั้นจึงถูกใช้อย่างผิดพลาด เงยหน้าขึ้นมองอ้างอิงสากล TCreditCardstd::forward


1

ฉันใช้กฎที่ค่อนข้างง่ายสำหรับกรณีทั่วไป: ใช้ copy สำหรับ POD (int, bool, double, ... ) และ const & สำหรับอย่างอื่น ...

และต้องการที่จะคัดลอกหรือไม่ไม่ได้รับการตอบสนองด้วยลายเซ็นของวิธีการ แต่เป็นมากกว่าสิ่งที่คุณทำกับพารา

struct A {
  A(const std::string& aValue, const std::string& another) 
    : copiedValue(aValue), justARef(another) {}
  std::string copiedValue;
  const std::string& justARef; 
};

ความแม่นยำสำหรับตัวชี้: ฉันแทบไม่เคยใช้เลย ข้อได้เปรียบเหนือ & คือสามารถเป็นโมฆะหรือกำหนดใหม่ได้


2
"ฉันใช้กฎที่ค่อนข้างง่ายสำหรับกรณีทั่วไป: ใช้ copy สำหรับ POD (int, bool, double, ... ) และ const & สำหรับอย่างอื่น" No. Just No.
Shoe

อาจจะเพิ่มถ้าคุณต้องการแก้ไขค่าโดยใช้ & (ไม่มี const) อย่างอื่นฉันไม่เห็น ... ความเรียบง่ายดีพอ แต่ถ้าคุณพูดอย่างนั้น ...
David Fleury

บางครั้งคุณต้องการแก้ไขตามประเภทดั้งเดิมอ้างอิงและบางครั้งคุณต้องทำสำเนาของวัตถุและบางครั้งคุณต้องย้ายวัตถุ คุณไม่สามารถลดทุกอย่างเป็น POD> ตามค่าและ UDT> โดยการอ้างอิง
รองเท้า

โอเคนี่เป็นเพียงสิ่งที่ฉันได้เพิ่มเข้าไป อาจเป็นคำตอบที่เร็วเกินไป
David Fleury

1

มันค่อนข้างไม่ชัดเจนสำหรับฉันสิ่งที่สำคัญที่สุดคือการส่งผ่านพารามิเตอร์

  • หากคุณต้องการแก้ไขตัวแปรที่ส่งผ่านภายในฟังก์ชัน / วิธีการ
    • คุณผ่านมันโดยการอ้างอิง
    • คุณส่งเป็นตัวชี้ (*)
  • หากคุณต้องการอ่านค่า / ตัวแปรที่ส่งผ่านภายในฟังก์ชัน / วิธีการ
    • คุณส่งผ่านโดยการอ้างอิง const
  • หากคุณต้องการแก้ไขค่าที่ส่งผ่านภายในฟังก์ชัน / วิธีการ
    • คุณผ่านปกติโดยการคัดลอกวัตถุ (**)

(*) พอยน์เตอร์อาจหมายถึงหน่วยความจำที่จัดสรรแบบไดนามิกดังนั้นเมื่อเป็นไปได้คุณควรต้องการการอ้างอิงมากกว่าพอยน์เตอร์แม้ว่าในท้ายที่สุดการอ้างอิงจะถูกนำไปใช้เป็นพอยน์เตอร์ก็ตาม

(**) "ปกติ" หมายถึงโดยตัวสร้างการคัดลอก (ถ้าคุณส่งวัตถุที่มีพารามิเตอร์ประเภทเดียวกัน) หรือโดยตัวสร้างปกติ (ถ้าคุณส่งประเภทที่เข้ากันได้สำหรับคลาส) myMethod(std::string)ตัวอย่างเช่นเมื่อคุณส่งผ่านวัตถุเป็นตัวสร้างการคัดลอกจะถูกใช้หากมีการstd::stringส่งผ่านไปยังวัตถุดังนั้นคุณต้องตรวจสอบให้แน่ใจว่ามีอยู่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.