ย้ายความหมายใน C ++ - ย้ายกลับของตัวแปรท้องถิ่น


11

ความเข้าใจของฉันคือว่าใน C ++ 11 เมื่อคุณส่งคืนตัวแปรโลคัลจากฟังก์ชันตามค่าคอมไพเลอร์ได้รับอนุญาตให้จัดการกับตัวแปรนั้นเป็นการอ้างอิงค่า r-value และ 'ย้าย' ออกจากฟังก์ชันเพื่อส่งคืน (ถ้า RVO / NRVO ไม่ได้เกิดขึ้นแทนแน่นอน)

คำถามของฉันคือจะไม่ทำลายรหัสที่มีอยู่นี้หรือไม่

พิจารณารหัสต่อไปนี้:

#include <iostream>
#include <string>

struct bar
{
  bar(const std::string& str) : _str(str) {}
  bar(const bar&) = delete;
  bar(bar&& other) : _str(std::move(other._str)) {other._str = "Stolen";}
  void print() {std::cout << _str << std::endl;}

  std::string _str;
};

struct foo
{
  foo(bar& b) : _b(b) {}
  ~foo() {_b.print();}

  bar& _b;
};

bar foobar()
{
  bar b("Hello, World!");
  foo f(b);

  return std::move(b);
}

int main()
{
  foobar();
  return EXIT_SUCCESS;
}

ความคิดของฉันคือว่ามันจะเป็นไปได้สำหรับ destructor ของวัตถุในท้องถิ่นเพื่ออ้างอิงวัตถุที่ถูกย้ายโดยปริยายและดังนั้นจึงเห็นวัตถุ 'ว่างเปล่า' โดยไม่คาดคิด ผมพยายามที่จะทดสอบนี้ (ดูhttp://ideone.com/ZURoeT ) แต่ผมได้ 'ถูกต้อง' ผลโดยไม่ต้องชัดเจนในstd::move foobar()ฉันคาดเดาว่าเป็นเพราะ NRVO แต่ฉันไม่ได้พยายามจัดเรียงรหัสใหม่เพื่อปิดการใช้งาน

ฉันถูกต้องหรือไม่ว่าการเปลี่ยนแปลงนี้ (ทำให้เกิดการย้ายออกจากฟังก์ชัน) เกิดขึ้นโดยปริยายและอาจทำให้รหัสที่มีอยู่เสียหายได้?

อัพเดทที่ นี่เป็นตัวอย่างที่แสดงสิ่งที่ฉันกำลังพูดถึง ลิงค์สองลิงค์ต่อไปนี้ใช้สำหรับรหัสเดียวกัน http://ideone.com/4GFIRu - C ++ 03 http://ideone.com/FcL2Xj - C ++ 11

ถ้าคุณดูผลลัพธ์มันต่างกัน

ดังนั้นฉันเดาว่าตอนนี้กลายเป็นคำถามนี้ได้รับการพิจารณาเมื่อเพิ่มการย้ายโดยนัยไปยังมาตรฐานและได้มีการตัดสินใจแล้วว่ามันก็โอเคที่จะเพิ่มการเปลี่ยนแปลงที่ทำลายนี้เนื่องจากโค้ดชนิดนี้หายากพอหรือไม่ ฉันยังสงสัยว่าคอมไพเลอร์ใด ๆ จะเตือนในกรณีเช่นนี้ ...


การย้ายจะต้องปล่อยให้วัตถุอยู่ในสถานะที่สามารถทำลายได้เสมอ
Zan Lynx

ใช่ แต่นั่นไม่ใช่คำถาม โค้ด Pre-c ++ 11 สามารถสันนิษฐานได้ว่าค่าของตัวแปรในตัวเครื่องจะไม่เปลี่ยนแปลงเพียงเพราะคืนค่ามาดังนั้นการย้ายโดยปริยายนี้อาจทำให้สมมติฐานนั้นผิด
Bwmat

นั่นคือสิ่งที่ฉันพยายามอธิบายในตัวอย่างของฉัน; ผ่าน destructors คุณสามารถตรวจสอบสถานะของ (ส่วนย่อยของ) ตัวแปรท้องถิ่นของฟังก์ชั่น 'หลังจาก' คำสั่งกลับมาดำเนินการ แต่ก่อนที่จะส่งกลับฟังก์ชั่นจริง
Bwmat

นี่เป็นคำถามที่ยอดเยี่ยมกับตัวอย่างที่คุณเพิ่ม ฉันหวังว่านี่จะได้รับคำตอบเพิ่มเติมจากมืออาชีพที่สามารถอธิบายเรื่องนี้ได้ ข้อเสนอแนะที่แท้จริงเพียงอย่างเดียวที่ฉันสามารถให้คือ: นี่คือเหตุผลที่วัตถุไม่ควรมีมุมมองที่ไม่ใช่การเป็นเจ้าของข้อมูล มีหลายวิธีในการเขียนโค้ดที่ดูไร้เดียงสาที่แยกออกมาเมื่อคุณให้มุมมองที่ไม่ได้เป็นเจ้าของวัตถุ (ตัวชี้หรือการอ้างอิงดิบ) ฉันสามารถอธิบายเรื่องนี้อย่างละเอียดด้วยคำตอบที่ถูกต้องหากคุณต้องการ แต่ฉันเดาว่านั่นไม่ใช่สิ่งที่คุณต้องการได้ยิน และ btw เป็นที่ทราบกันดีอยู่แล้วว่า 11 สามารถทำลายรหัสที่มีอยู่เช่นโดยการกำหนดคำหลักใหม่
Nir Friedman

ใช่ฉันรู้ว่า C ++ 11 ไม่เคยอ้างสิทธิ์ว่าจะไม่ทำลายรหัสเก่า แต่นี่มันค่อนข้างบอบบางและจะพลาดได้ง่ายจริงๆ (ไม่มีข้อผิดพลาดของคอมไพเลอร์คำเตือน segfaults)
Bwmat

คำตอบ:


8

Scott Meyers โพสต์ไปยัง comp.lang.c ++ (สิงหาคม 2010) เกี่ยวกับปัญหาที่ตัวสร้างการย้ายโดยปริยายสามารถทำลายค่าคงที่คลาส C ++ 03:

struct X
{
  // invariant: v.size() == 5
  X() : v(5) {}

  ~X() { std::cout << v[0] << std::endl; }

private:    
  std::vector<int> v;
};

int main()
{
    std::vector<X> y;
    y.push_back(X()); // X() rvalue: copied in C++03, moved in C++0x
}

นี่คือปัญหาคือใน C ++ 03 Xมีค่าคงที่ที่vสมาชิกมีองค์ประกอบ 5 ค่าเสมอ X::~X()นับค่าคงที่ แต่ตัวสร้างการย้ายที่เพิ่งถูกแนะนำย้ายมาจากvดังนั้นจึงตั้งค่าความยาวเป็นศูนย์

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

C ++ 11 พยายามที่จะบรรลุความสมดุลระหว่างการแบ่งรหัสที่มีอยู่บางส่วนและให้การเพิ่มประสิทธิภาพที่เป็นประโยชน์ตามตัวสร้างการย้าย

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

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

มันน่าดึงดูดที่จะคิดว่าการป้องกันการสร้างตัวสร้างการเคลื่อนย้ายโดยปริยายเมื่อตัวทำลายที่ผู้ใช้กำหนดเองมีอยู่ก็เพียงพอแล้ว แต่มันก็ไม่เป็นความจริง ( N3153 - การย้ายโดยปริยายต้องทำเพื่อดูรายละเอียดเพิ่มเติม)

ในN3174 - เพื่อย้ายหรือไม่ย้าย Stroupstrup พูดว่า:

ฉันคิดว่านี่เป็นปัญหาการออกแบบภาษามากกว่าปัญหาความเข้ากันได้แบบย้อนหลังอย่างง่าย มันง่ายที่จะหลีกเลี่ยงการทำลายรหัสเก่า (เช่นเพียงแค่ลบการดำเนินการย้ายออกจาก C ++ 0x) แต่ฉันเห็นว่าการทำให้ C ++ 0x เป็นภาษาที่ดีขึ้นโดยการดำเนินการย้ายที่แพร่หลายเป็นเป้าหมายหลัก รหัส +98

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