ความแตกต่างระหว่าง std :: move และ std :: forward


160

ฉันเห็นสิ่งนี้ที่นี่: Move Constructor โทรหา Move Constructor ระดับฐาน

มีคนอธิบายได้ไหม:

  1. ความแตกต่างระหว่างstd::moveและstd::forwardโดยเฉพาะอย่างยิ่งกับตัวอย่างรหัสบางอย่าง?
  2. วิธีคิดเกี่ยวกับมันง่าย ๆ และเมื่อใดควรใช้แบบไหน

1
ดูคำถามสองข้อที่เกี่ยวข้องเหล่านี้: ฉันควรใช้ std :: move หรือ std :: forward ในการย้ายตัวดำเนินการตัวจัดการ / การมอบหมาย และstd :: forward ทำงานอย่างไร? (รวมทั้งคำถามซ้ำซ้อน)
Xeo

"วิธีคิดอย่างง่ายดายและใช้เมื่อใด" คุณใช้moveเมื่อคุณต้องการย้ายค่าและforwardเมื่อคุณต้องการใช้การส่งต่อที่สมบูรณ์แบบ นี่ไม่ใช่วิทยาศาสตร์จรวดที่นี่;)
Nicol Bolas

ย้าย () ดำเนินการโยนที่ไม่มีเงื่อนไขที่เป็นไปข้างหน้า () ดำเนินการโยนขึ้นอยู่กับพารามิเตอร์ที่ผ่าน
RaGa__M

คำตอบ:


159

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

std::forwardมีกรณีการใช้งานเพียงครั้งเดียว: เพื่อ cast พารามิเตอร์ฟังก์ชั่น templated (ภายในฟังก์ชั่น) กับหมวดหมู่ค่า (lvalue หรือ rvalue) โทรที่ใช้ในการส่งมัน สิ่งนี้อนุญาตให้ส่งผ่านอาร์กิวเมนต์ rvalue เป็น rvalues ​​และ lvalues ​​ถูกส่งผ่านเป็น lvalues ​​แบบแผนเรียกว่า "การส่งต่อที่สมบูรณ์แบบ"

เพื่อแสดง :

void overloaded( int const &arg ) { std::cout << "by lvalue\n"; }
void overloaded( int && arg ) { std::cout << "by rvalue\n"; }

template< typename t >
/* "t &&" with "t" being template param is special, and  adjusts "t" to be
   (for example) "int &" or non-ref "int" so std::forward knows what to do. */
void forwarding( t && arg ) {
    std::cout << "via std::forward: ";
    overloaded( std::forward< t >( arg ) );
    std::cout << "via std::move: ";
    overloaded( std::move( arg ) ); // conceptually this would invalidate arg
    std::cout << "by simple passing: ";
    overloaded( arg );
}

int main() {
    std::cout << "initial caller passes rvalue:\n";
    forwarding( 5 );
    std::cout << "initial caller passes lvalue:\n";
    int x = 5;
    forwarding( x );
}

ในขณะที่ฮาวเวิร์ดกล่าวถึงก็มีความคล้ายคลึงกันเนื่องจากฟังก์ชั่นทั้งสองนี้ใช้เพื่ออ้างอิง แต่นอกเหนือจากกรณีการใช้งานเฉพาะเหล่านี้ (ซึ่งครอบคลุมถึง 99.9% ของประโยชน์ของการอ้างอิงแบบ rvlue) คุณควรใช้static_castโดยตรงและเขียนคำอธิบายที่ดีเกี่ยวกับสิ่งที่คุณกำลังทำ


ผมไม่แน่ใจว่ามันถูกต้องที่std::forward's เฉพาะกรณีการใช้งานคือการส่งต่อที่สมบูรณ์แบบของฟังก์ชั่นการขัดแย้ง ฉันพบกับสถานการณ์ที่ฉันต้องการส่งต่อสิ่งอื่น ๆ อย่างสมบูรณ์แบบเช่นสมาชิกวัตถุ
Geoff Romer

@GeoffRomer สมาชิกของพารามิเตอร์หรือไม่ คุณมีตัวอย่างหรือไม่
Potatoswatter

ฉันโพสต์ตัวอย่างเป็นคำถามแยกต่างหาก: stackoverflow.com/questions/20616958/…
Geoff Romer

อืมดังนั้นถ้าฉันเข้าใจสิ่งนี้อย่างถูกต้องก็หมายความว่าฉันสามารถเขียนฟังก์ชันเดียวไปข้างหน้า () โดยที่ forward (5) ทำงานบน 5 ราวกับว่าฉันผ่านมันโดยค่าที่ forward (x) ทำงานบน x ราวกับว่าฉันผ่าน การอ้างอิงโดยไม่ต้องเขียนโอเวอร์โหลดอย่างชัดเจน
iheanyi

@iheanyi ใช่นั่นคือความคิด! แต่ต้องเป็นเทมเพลตไม่ใช่แค่ฟังก์ชั่นธรรมดา
Potatoswatter

63

ทั้งสองstd::forwardและstd::moveไม่ได้เป็นเพียงแค่ร่าย

X x;
std::move(x);

ด้านบนปลดเปลื้องการแสดงออก lvalue xของประเภท X เพื่อการแสดงออก rvalue ของประเภท X (xvalue ที่จะต้องแน่นอน) moveยังสามารถยอมรับค่า rvalue:

std::move(make_X());

และในกรณีนี้มันเป็นฟังก์ชั่นระบุตัวตน: รับค่า rvalue ของประเภท X และส่งกลับค่า rvalue ของประเภท X

ด้วยstd::forwardคุณสามารถเลือกปลายทางได้:

X x;
std::forward<Y>(x);

ปลดล็อกนิพจน์ lvalue xของ type X ไปยังนิพจน์ประเภท Y มีข้อ จำกัด เกี่ยวกับสิ่งที่ Y สามารถเป็นได้

Y สามารถเป็นฐานของ X ที่สามารถเข้าถึงได้หรือการอ้างอิงไปยังฐานของ X. Y สามารถเป็น X หรืออ้างอิงถึง X หนึ่งไม่สามารถทิ้งตัวระบุ cv-qualifier ด้วยforwardแต่สามารถเพิ่ม cv-qualifier ได้ Y ไม่สามารถเป็นประเภทที่แปลงได้จาก X เท่านั้นยกเว้นผ่านการแปลงฐานที่สามารถเข้าถึงได้

ถ้า Y เป็นการอ้างอิง lvalue ผลลัพธ์จะเป็นนิพจน์ lvalue หาก Y ไม่ใช่การอ้างอิง lvalue ผลลัพธ์จะเป็นนิพจน์ rvalue (xvalue ที่จะแม่นยำ)

forwardสามารถรับอาร์กิวเมนต์ rvalue ก็ต่อเมื่อ Y ไม่ใช่การอ้างอิง lvalue นั่นคือคุณไม่สามารถใช้ค่า rvalue เป็น lvalue ได้ นี่คือเหตุผลด้านความปลอดภัยเนื่องจากการทำเช่นนั้นมักนำไปสู่การอ้างอิงที่ห้อยอยู่ แต่การส่งค่า rvalue ไปยัง rvalue นั้นใช้ได้และอนุญาต

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


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

1
เกี่ยวกับmove: stackoverflow.com/a/7028318/576911 สำหรับforwardหากคุณส่งผ่านค่า lvalue API ของคุณควรตอบสนองราวกับว่าได้รับ lvalue โดยปกตินี่หมายความว่าค่าจะไม่ได้รับการแก้ไข แต่ถ้ามันไม่ใช่ค่า lvalue API ของคุณอาจแก้ไขได้ หากคุณผ่านค่า rvalue ปกติจะหมายความว่า API ของคุณอาจถูกย้ายไปจากนั้นและจะใช้stackoverflow.com/a/7028318/576911
Howard Hinnant

21

std::forwardใช้ในการส่งต่อพารามิเตอร์ตามที่มันส่งผ่านไปยังฟังก์ชั่น เช่นเดียวกับที่แสดงที่นี่:

เมื่อใดที่จะใช้ std :: forward เพื่อส่งต่อข้อโต้แย้ง?

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

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