std :: move () คืออะไรและควรใช้เมื่อใด


656
  1. มันคืออะไร?
  2. มันทำอะไร?
  3. ควรใช้เมื่อใด

ลิงค์ที่ดีชื่นชม


43
Bjarne Stroustrup อธิบายการเคลื่อนไหวในบทนำบทสรุปเพื่ออ้างอิง
Rvalue


12
คำถามนี้หมายถึงstd::move(T && t); นอกจากนี้ยังมีอยู่ซึ่งเป็นขั้นตอนที่เกี่ยวข้องกับการstd::move(InputIt first, InputIt last, OutputIt d_first) std::copyฉันชี้ให้เห็นว่าคนอื่นจะไม่สับสนเหมือนฉันเมื่อแรกพบกับstd::moveการโต้แย้งสาม en.cppreference.com/w/cpp/algorithm/move
josaphatv

คำตอบ:


287

หน้า Wikipedia บนการอ้างอิงค่า C + 11 R และตัวสร้างการย้าย

  1. ใน C ++ 11 นอกเหนือจากการคัดลอกตัวสร้างวัตถุสามารถมีตัวสร้างการย้าย
    (และนอกเหนือจากการคัดลอกตัวดำเนินการกำหนดพวกเขามีตัวดำเนินการกำหนดค่าย้าย)
  2. ตัวสร้างการย้ายถูกใช้แทนตัวสร้างการคัดลอกหากวัตถุมีประเภท "rvalue-reference" ( Type &&)
  3. std::move() เป็นนักแสดงที่สร้างการอ้างอิงค่ากับวัตถุเพื่อให้สามารถเคลื่อนที่จากวัตถุได้

เป็นวิธี C ++ ใหม่ในการหลีกเลี่ยงการทำสำเนา ตัวอย่างเช่นการใช้ตัวสร้างการย้าย a std::vectorสามารถคัดลอกตัวชี้ภายในไปยังข้อมูลไปยังวัตถุใหม่โดยปล่อยให้วัตถุที่ถูกย้ายอยู่ในสถานะที่ย้ายจากสถานะดังนั้นจึงไม่คัดลอกข้อมูลทั้งหมด นี่จะเป็น C ++ - ถูกต้อง

ลองใช้ Google เพื่อค้นหาซีแมนติกส์การเคลื่อนค่าการส่งต่อที่สมบูรณ์แบบ


40
ความหมายของการย้ายต้องการวัตถุที่ถูกย้ายยังคงใช้ได้ซึ่งไม่ใช่สถานะที่ไม่ถูกต้อง (เหตุผล: มันยังคงมีการทำลายล้างทำให้มันทำงาน)
GManNickG

13
@GMan: มันจะต้องอยู่ในสถานะที่ปลอดภัยต่อการทำลายล้าง แต่ AFAIK ไม่จำเป็นต้องใช้งานอย่างอื่นอีก
Zan Lynx

8
@ZanLynx: ถูกต้อง โปรดทราบว่าไลบรารีมาตรฐานต้องการวัตถุที่ถูกย้ายเพิ่มเติมนั้นสามารถกำหนดได้ แต่สำหรับวัตถุที่ใช้ใน stdlib เท่านั้นไม่ใช่ข้อกำหนดทั่วไป
GManNickG

25
-1 "std :: move () เป็นวิธี C ++ 11 ที่จะใช้ซีแมนทิกส์การย้าย"โปรดแก้ไขสิ่งนั้น std::move()ไม่ใช่วิธีใช้ความหมายของการย้ายความหมายของการย้ายจะดำเนินการอย่างโปร่งใสไปยังโปรแกรมเมอร์ moveมีเพียงนักแสดงที่จะส่งค่าจากจุดหนึ่งไปยังอีกจุดหนึ่งซึ่งจะไม่ใช้ lvalue ดั้งเดิมอีกต่อไป
Manu343726

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

241

1. "มันคืออะไร"

ในขณะที่std::move() เป็นเทคนิคที่ฟังก์ชั่น - ผมจะบอกว่ามันไม่ได้จริงๆฟังก์ชั่น มันเป็นตัวแปลงระหว่างวิธีที่คอมไพเลอร์พิจารณาค่าของนิพจน์

2. "ทำอะไรได้บ้าง"

สิ่งแรกที่ควรทราบคือstd::move() ไม่ได้ย้ายอะไรจริงๆ มันจะแปลงการแสดงออกจากการเป็นlvalue (เช่นตัวแปรชื่อ) จะเป็นXvalue xvalue บอกคอมไพเลอร์:

คุณสามารถปล้นฉันย้ายสิ่งที่ฉันถือและใช้ที่อื่น (เนื่องจากฉันกำลังจะถูกทำลายในไม่ช้า) "

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

นอกจากนี้คุณยังสามารถย้ายจากprvalue (เช่นชั่วคราวที่คุณผ่านไป) แต่ไม่ค่อยมีประโยชน์

3. "ควรใช้เมื่อใด"

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

การใช้งานทั่วไปคือทรัพยากร 'ย้าย' จากวัตถุหนึ่งไปอีกวัตถุหนึ่งแทนที่จะคัดลอก @Gillailla เชื่อมโยงไปยังหน้านี้ซึ่งมีตัวอย่างสั้น ๆ ที่ตรงไปตรงมา: การแลกเปลี่ยนสองวัตถุที่มีการคัดลอกน้อย

template <class T>
swap(T& a, T& b) {
    T tmp(a);   // we now have two copies of a
    a = b;      // we now have two copies of b (+ discarded a copy of a)
    b = tmp;    // we now have two copies of tmp (+ discarded a copy of b)
}

การใช้การย้ายช่วยให้คุณสลับทรัพยากรแทนที่จะคัดลอกไปมา:

template <class T>
swap(T& a, T& b) {
    T tmp(std::move(a));
    a = std::move(b);   
    b = std::move(tmp);
}

คิดถึงสิ่งที่เกิดขึ้นเมื่อTพูดvector<int>ขนาด n ในเวอร์ชันแรกที่คุณอ่านและเขียนอิลิเมนต์ 3 * n ในเวอร์ชันที่สองคุณจะอ่านและเขียนเพียง 3 พอยน์เตอร์ไปยังบัฟเฟอร์เวกเตอร์รวมถึงขนาดบัฟเฟอร์ 3 ตัว แน่นอนว่าผู้เรียนTต้องรู้วิธีการเคลื่อนไหว คลาสของคุณควรมีโอเปอเรเตอร์การกำหนดและตัวสร้างการย้ายสำหรับคลาสTเพื่อให้สิ่งนี้ใช้ได้


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

7
@TitoneMaurice: ยกเว้นว่าไม่ใช่สำเนา - เนื่องจากค่าดั้งเดิมไม่สามารถใช้งานได้อีกต่อไป
einpoklum

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

17
@rubenvb Zebra ไม่ผิดเลย ในขณะที่มันเป็นความจริงที่วัตถุ cannabilised ดั้งเดิมมักจะก่อวินาศกรรมโดยจงใจเพื่อหลีกเลี่ยงข้อผิดพลาดที่สับสน (เช่นตั้งค่าพอยน์เตอร์ของมันเป็น nullptr เพื่อส่งสัญญาณว่ามันไม่ได้เป็นเจ้าของ Pointees) ความจริงที่ว่า ไปยังปลายทาง (และจงใจหลีกเลี่ยงการทำอะไรกับปวงต์) เป็นการระลึกถึงสำเนาตื้น ๆ ในความเป็นจริงฉันจะไปไกลเท่าที่จะบอกว่าการย้ายเป็นสำเนาตื้นตามตัวเลือกโดยการทำลายตัวเองบางส่วนของแหล่งที่มา (ต่อ)
Lightness Races ใน Orbit

3
(ต่อ)หากเราอนุญาตให้ใช้คำจำกัดความนี้ (และฉันชอบมากกว่านี้) การสังเกตของ @ Zebrafish นั้นไม่ผิดเพียงเล็กน้อยเท่านั้น
การแข่งขัน Lightness ใน Orbit

146

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

ลิงค์นี้ช่วยฉันได้มาก:

http://thbecker.net/articles/rvalue_references/section_01.html

ฉันขอโทษถ้าคำตอบของฉันมาสายเกินไป แต่ฉันก็กำลังมองหาลิงค์ที่ดีสำหรับ std :: move และฉันก็พบลิงค์ด้านบน "austere" เล็กน้อย

สิ่งนี้ให้ความสำคัญกับการอ้างอิง r-value ในบริบทที่คุณควรใช้และฉันคิดว่ามันมีรายละเอียดมากกว่านั่นคือสาเหตุที่ฉันต้องการแชร์ลิงก์นี้ที่นี่


26
ลิงค์ที่ดี ฉันพบบทความวิกิพีเดียอยู่เสมอและลิงก์อื่น ๆ ที่ฉันพบเจอค่อนข้างสับสนเพราะพวกเขาเพียงแค่โยนความจริงใส่คุณเพื่อให้เข้าใจว่าความหมาย / เหตุผลที่แท้จริงคืออะไร ในขณะที่ "ความหมายย้าย" ในตัวสร้างค่อนข้างชัดเจนรายละเอียดทั้งหมดที่เกี่ยวกับการส่ง && - ค่ารอบ ๆ ไม่ ... ดังนั้นคำอธิบายลักษณะการสอนเป็นสิ่งที่ดีมาก
คริสเตียน Stieber

66

Q: อะไรคือสิ่งที่std::move?

ตอบ: std::move()เป็นฟังก์ชันจาก C ++ Standard Library สำหรับการส่งไปยังการอ้างอิง rvalue

Simplisticly std::move(t)เทียบเท่ากับ:

static_cast<T&&>(t);

ค่า rvalue เป็นแบบชั่วคราวที่ไม่คงอยู่เกินกว่านิพจน์ที่กำหนดไว้เช่นผลลัพธ์ของฟังก์ชันระดับกลางซึ่งไม่เคยถูกเก็บไว้ในตัวแปร

int a = 3; // 3 is a rvalue, does not exist after expression is evaluated
int b = a; // a is a lvalue, keeps existing after expression is evaluated

มีการนำไปใช้สำหรับ std :: move () ในN2027: "A Introduction to Rvalue Reference" โดยย่อดังนี้:

template <class T>
typename remove_reference<T>::type&&
std::move(T&& a)
{
    return a;
}

ตามที่คุณเห็นแล้วstd::moveคืนค่าT&&ไม่ว่าจะถูกเรียกด้วยค่า ( T) ประเภทการอ้างอิง ( T&) หรือการอ้างอิงค่า rvalue (T&& )

ถาม: มันทำอะไร

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

foo(3 * 5); // obviously, you are calling foo with a temporary (rvalue)

int a = 3 * 5;
foo(a);     // how to tell the compiler to treat `a` as an rvalue?
foo(std::move(a)); // will call `foo(int&& a)` rather than `foo(int a)` or `foo(int& a)`

มันไม่ได้ทำอะไร:

  • ทำสำเนาของอาร์กิวเมนต์
  • เรียกตัวสร้างสำเนา
  • เปลี่ยนวัตถุอาร์กิวเมนต์

ถาม: ควรใช้เมื่อใด

ตอบ: คุณควรใช้ std::moveถ้าคุณต้องการเรียกใช้ฟังก์ชันที่รองรับซีแมนทิกส์ย้ายด้วยอาร์กิวเมนต์ที่ไม่ใช่ rvalue (นิพจน์ชั่วคราว)

นี่เป็นคำถามติดตามผลต่อไปนี้สำหรับฉัน:

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

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

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

  • ในสถานการณ์ใดที่ฉันต้องการบอกคอมไพเลอร์ว่าควรปฏิบัติต่อตัวแปรเป็นค่า rvalue หรือไม่? สิ่งนี้มักจะเกิดขึ้นในฟังก์ชั่นเทมเพลตหรือห้องสมุดซึ่งคุณรู้ว่าผลลัพธ์ระดับกลางจะได้รับการกู้คืน


2
+1 ขนาดใหญ่สำหรับตัวอย่างโค้ดที่มีซีแมนทิกส์ในความคิดเห็น คำตอบยอดนิยมอื่น ๆ กำหนด std :: ย้ายโดยใช้ "ย้าย" ตัวเอง - ไม่ได้ชี้แจงอะไรจริงๆ! --- ฉันเชื่อว่ามันคุ้มค่าที่จะกล่าวว่าการไม่คัดลอกอาร์กิวเมนต์หมายความว่าไม่สามารถใช้ค่าดั้งเดิมได้อย่างน่าเชื่อถือ
ty

34

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

ดังนั้น std :: move นั้นถูกใช้เป็นเสมือนสารตั้งต้นเพื่อใช้ semantics ของการย้าย ความหมายของการย้ายเป็นหลักเป็นวิธีที่มีประสิทธิภาพในการจัดการกับวัตถุชั่วคราว

พิจารณาวัตถุ A = B + C + D + E + F;

นี่เป็นโค้ดที่ดูดี แต่ E + F สร้างวัตถุชั่วคราว จากนั้น D + temp จะสร้างวัตถุชั่วคราวอื่นขึ้นเรื่อย ๆ ในแต่ละโอเปอเรเตอร์ "+" ปกติของคลาสจะมีการทำสำเนาลึก

ตัวอย่างเช่น

Object Object::operator+ (const Object& rhs) {
    Object temp (*this);
    // logic for adding
    return temp;
}

การสร้างวัตถุชั่วคราวในฟังก์ชั่นนี้ไม่มีประโยชน์ - วัตถุชั่วคราวเหล่านี้จะถูกลบในตอนท้ายของบรรทัดอย่างไรก็ตามเมื่อออกนอกขอบเขต

เราสามารถใช้การย้ายความหมายเพื่อ "ปล้น" วัตถุชั่วคราวและทำสิ่งที่ต้องการ

 Object& Object::operator+ (Object&& rhs) {
     // logic to modify rhs directly
     return rhs;
 }

หลีกเลี่ยงการทำสำเนาลึกที่ไม่จำเป็น ด้วยการอ้างอิงถึงตัวอย่างเพียงส่วนเดียวที่การทำสำเนาลึกเกิดขึ้นตอนนี้ E + F ส่วนที่เหลือใช้ซีแมนทิกส์การย้าย ตัวสร้างการย้ายหรือโอเปอเรเตอร์การมอบหมายต้องถูกนำมาใช้เพื่อกำหนดผลลัพธ์ให้กับ A


3
คุณพูดเกี่ยวกับความหมายของการย้าย คุณควรเพิ่มคำตอบของคุณตามที่ std :: move สามารถใช้ได้เนื่องจากคำถามถามเกี่ยวกับสิ่งนั้น
Koushik Shetty

2
@Koushik std :: move ไม่ได้ทำอะไรมาก - แต่ใช้เพื่อนำอรรถศาสตร์การย้ายมาใช้ หากคุณไม่ทราบเกี่ยวกับ std :: move คุณอาจไม่รู้ว่าความหมายของการย้าย
user929404

1
"ไม่ได้ทำอะไรมาก" (ใช่เพียง static_cast เพื่ออ้างอิง rvalue) มันทำอะไรจริงและ y ก็เป็นสิ่งที่ OP ถาม คุณไม่จำเป็นต้องรู้ว่า std :: move ทำงานอย่างไร แต่คุณต้องรู้ว่าซีแมนติกส์การย้ายอะไรทำ นอกจากนี้ "แต่ใช้เพื่อนำความหมายของการย้าย" ไปใช้ ทราบความหมายย้ายและคุณจะเข้าใจ std :: ย้ายอย่างอื่นไม่ ย้ายเพียงแค่ช่วยในการเคลื่อนไหวและตัวเองใช้ความหมายย้าย std :: move ไม่ทำอะไรเลยนอกจากแปลงอาร์กิวเมนต์เป็น rvalue reference ซึ่งเป็นสิ่งที่ semantics ต้องการ
Koushik Shetty

10
"แต่ E + F สร้างวัตถุชั่วคราว" - ผู้ประกอบการ+ไปจากซ้ายไปขวาไม่ใช่จากขวาไปซ้าย ดังนั้นB+Cจะเป็นคนแรก!
Ajay

8

"มันคืออะไร?" และ "มันทำอะไร" ได้รับการอธิบายข้างต้น

ฉันจะให้ตัวอย่างของ"เมื่อใดควรใช้"

ตัวอย่างเช่นเรามีคลาสที่มีทรัพยากรมากมายเช่นอาเรย์ขนาดใหญ่ในคลาส

class ResHeavy{ //  ResHeavy means heavy resource
    public:
        ResHeavy(int len=10):_upInt(new int[len]),_len(len){
            cout<<"default ctor"<<endl;
        }

        ResHeavy(const ResHeavy& rhs):_upInt(new int[rhs._len]),_len(rhs._len){
            cout<<"copy ctor"<<endl;
        }

        ResHeavy& operator=(const ResHeavy& rhs){
            _upInt.reset(new int[rhs._len]);
            _len = rhs._len;
            cout<<"operator= ctor"<<endl;
        }

        ResHeavy(ResHeavy&& rhs){
            _upInt = std::move(rhs._upInt);
            _len = rhs._len;
            rhs._len = 0;
            cout<<"move ctor"<<endl;
        }

    // check array valid
    bool is_up_valid(){
        return _upInt != nullptr;
    }

    private:
        std::unique_ptr<int[]> _upInt; // heavy array resource
        int _len; // length of int array
};

รหัสทดสอบ:

void test_std_move2(){
    ResHeavy rh; // only one int[]
    // operator rh

    // after some operator of rh, it becomes no-use
    // transform it to other object
    ResHeavy rh2 = std::move(rh); // rh becomes invalid

    // show rh, rh2 it valid
    if(rh.is_up_valid())
        cout<<"rh valid"<<endl;
    else
        cout<<"rh invalid"<<endl;

    if(rh2.is_up_valid())
        cout<<"rh2 valid"<<endl;
    else
        cout<<"rh2 invalid"<<endl;

    // new ResHeavy object, created by copy ctor
    ResHeavy rh3(rh2);  // two copy of int[]

    if(rh3.is_up_valid())
        cout<<"rh3 valid"<<endl;
    else
        cout<<"rh3 invalid"<<endl;
}

เอาท์พุทดังต่อไปนี้:

default ctor
move ctor
rh invalid
rh2 valid
copy ctor
rh3 valid

เราจะเห็นว่าstd::moveด้วยการmove constructorทำให้ทรัพยากรการแปลงได้อย่างง่ายดาย

มีstd::moveประโยชน์ที่ไหน?

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

นอกจากนี้ยังมีประโยชน์หากเราต้องการย้ายเนื้อหาที่จัดการโดยตัวชี้สมาร์ทหนึ่งไปยังอีกที่หนึ่ง

อ้างถึง:


0

นี่คือตัวอย่างเต็มรูปแบบโดยใช้ std :: move สำหรับเวกเตอร์ที่กำหนดเอง (ง่าย)

ผลลัพธ์ที่คาดหวัง:

 c: [10][11]
 copy ctor called
 copy of c: [10][11]
 move ctor called
 moved c: [10][11]

รวบรวมเป็น:

  g++ -std=c++2a -O2 -Wall -pedantic foo.cpp

รหัส:

#include <iostream>
#include <algorithm>

template<class T> class MyVector {
private:
    T *data;
    size_t maxlen;
    size_t currlen;
public:
    MyVector<T> () : data (nullptr), maxlen(0), currlen(0) { }
    MyVector<T> (int maxlen) : data (new T [maxlen]), maxlen(maxlen), currlen(0) { }

    MyVector<T> (const MyVector& o) {
        std::cout << "copy ctor called" << std::endl;
        data = new T [o.maxlen];
        maxlen = o.maxlen;
        currlen = o.currlen;
        std::copy(o.data, o.data + o.maxlen, data);
    }

    MyVector<T> (const MyVector<T>&& o) {
        std::cout << "move ctor called" << std::endl;
        data = o.data;
        maxlen = o.maxlen;
        currlen = o.currlen;
    }

    void push_back (const T& i) {
        if (currlen >= maxlen) {
            maxlen *= 2;
            auto newdata = new T [maxlen];
            std::copy(data, data + currlen, newdata);
            if (data) {
                delete[] data;
            }
            data = newdata;
        }
        data[currlen++] = i;
    }

    friend std::ostream& operator<<(std::ostream &os, const MyVector<T>& o) {
        auto s = o.data;
        auto e = o.data + o.currlen;;
        while (s < e) {
            os << "[" << *s << "]";
            s++;
        }
        return os;
    }
};

int main() {
    auto c = new MyVector<int>(1);
    c->push_back(10);
    c->push_back(11);
    std::cout << "c: " << *c << std::endl;
    auto d = *c;
    std::cout << "copy of c: " << d << std::endl;
    auto e = std::move(*c);
    delete c;
    std::cout << "moved c: " << e << std::endl;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.