จะบังคับใช้ความหมายการย้ายได้อย่างไรเมื่อเวกเตอร์เติบโตขึ้น


93

ฉันมีstd::vectorวัตถุของชั้นAหนึ่ง คลาสไม่สำคัญและมีตัวสร้างการคัดลอกและย้ายตัวสร้างที่กำหนดไว้

std::vector<A>  myvec;

ถ้าฉันเติมเวกเตอร์ด้วยAวัตถุ (โดยใช้เช่นmyvec.push_back(a)) เวกเตอร์จะมีขนาดใหญ่ขึ้นโดยใช้ตัวสร้างการคัดลอกA( const A&)เพื่อสร้างอินสแตนซ์สำเนาใหม่ขององค์ประกอบในเวกเตอร์

ฉันสามารถบังคับใช้ตัวสร้างการย้ายของคลาสAแทนได้หรือไม่?


5
คุณสามารถทำได้โดยใช้การใช้เวกเตอร์ที่รับรู้การเคลื่อนไหว
K-ballo

2
คุณช่วยอธิบายให้ชัดเจนขึ้นหน่อยได้ไหมว่าจะทำอย่างไร
Bertwim van Beest

1
คุณเพียงแค่ใช้การใช้งานเวกเตอร์ที่รับรู้การเคลื่อนไหว ดูเหมือนว่าการใช้งานไลบรารีมาตรฐานของคุณ (มันคืออะไร?) จะไม่ทราบ คุณสามารถลองใช้ตู้คอนเทนเนอร์แบบเคลื่อนย้ายได้จาก Boost
K-ballo

1
ฉันใช้ gcc 4.5.1 ซึ่งรับรู้การเคลื่อนไหว
Bertwim van Beest

ในโค้ดของฉันมันได้ผลในการทำให้ตัวสร้างการคัดลอกเป็นแบบส่วนตัวแม้ว่าตัวสร้างการย้ายจะไม่มี "noexcept" ที่ชัดเจน
Arne

คำตอบ:


129

คุณจำเป็นต้องแจ้ง c ++ (เฉพาะstd::vector) ที่คอนสตรัคย้ายและ destructor noexceptของคุณไม่ได้โยนใช้ จากนั้นตัวสร้างการย้ายจะถูกเรียกเมื่อเวกเตอร์โตขึ้น

นี่คือวิธีการประกาศและใช้ตัวกำหนดย้ายที่เคารพโดยstd::vector:

A(A && rhs) noexcept { 
  std::cout << "i am the move constr" <<std::endl;
  ... some code doing the move ...  
  m_value=std::move(rhs.m_value) ; // etc...
}

ถ้าสร้างไม่ได้noexcept, std::vectorไม่สามารถใช้งานได้ตั้งแต่นั้นมาก็ไม่สามารถให้การค้ำประกันยกเว้นเรียกร้องโดยมาตรฐาน

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับสิ่งที่กล่าวในมาตรฐานโปรดอ่าน ความหมายและข้อยกเว้นของ C ++ Move

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

แก้ไขโดยปกติค่าเริ่มต้นคือสิ่งที่คุณต้องการ: ย้ายทุกอย่างที่สามารถย้ายได้คัดลอกส่วนที่เหลือ หากต้องการขออย่างชัดเจนให้เขียน

A(A && rhs) = default;

การทำเช่นนั้นคุณจะได้รับ noexcept เมื่อเป็นไปได้: ตัวสร้าง Move เริ่มต้นถูกกำหนดให้เป็น noexcept หรือไม่?

โปรดทราบว่า Visual Studio 2015 เวอร์ชันก่อนหน้าและเก่ากว่าไม่รองรับแม้ว่าจะสนับสนุนความหมายการย้ายก็ตาม


ออกจากดอกเบี้ยอย่างไรไม่ Impl "สาระน่ารู้" ไม่ว่าจะเป็นvalue_type's ย้าย ctor คือnoexcept? บางทีภาษาอาจ จำกัด การเรียกใช้ฟังก์ชันผู้สมัครที่กำหนดไว้เมื่อขอบเขตการโทรเป็นnoexceptฟังก์ชันด้วย?
Lightness Races ใน Orbit

1
@LightnessRacesinOrbit ผมถือว่ามันเป็นเพียงแค่ทำอะไรบางอย่างเช่นen.cppreference.com/w/cpp/types/is_move_constructible สามารถมีตัวสร้างการย้ายได้เพียงตัวเดียวดังนั้นจึงควรกำหนดให้ชัดเจนโดยการประกาศ
Johan Lundberg

@LightnessRacesinOrbit ตั้งแต่นั้นมาฉันได้เรียนรู้ว่าไม่มีวิธี (มาตรฐาน / มีประโยชน์) ที่จะรู้ว่ามีตัวnoexceptสร้างการย้ายหรือไม่ is_nothrow_move_constructibleจะเป็นจริงถ้ามีตัวnothrowสร้างการคัดลอก ฉันไม่ทราบถึงกรณีที่แท้จริงของตัวnothrowสร้างสำเนาราคาแพงดังนั้นจึงไม่ชัดเจนว่ามันสำคัญจริงๆ
Johan Lundberg

ไม่ได้ผลสำหรับฉัน ฟังก์ชัน destructor, move constructor และ move assigment ของฉันจะถูกทำเครื่องหมายnoexceptทั้งในส่วนหัวและการนำไปใช้งานและเมื่อฉันทำการ push_back (std:; move) มันยังคงเรียกตัวสร้างการคัดลอก ฉันฉีกผมออกจากที่นี่
AlastairG

1
@ โจฮันฉันพบปัญหา ฉันใช้std::move()กับการpush_back()โทรผิด หนึ่งในช่วงเวลาที่คุณกำลังมองหาปัญหาอย่างหนักซึ่งคุณไม่เห็นข้อผิดพลาดที่ชัดเจนตรงหน้าคุณ แล้วก็เป็นเวลาอาหารกลางวันและฉันลืมลบความคิดเห็นของฉัน
AlastairG

17

ที่น่าสนใจ GCC 4.7.2 noexceptของเวกเตอร์เพียงใช้คอนสตรัคย้ายถ้าทั้งสองคอนสตรัคย้ายและเตาเผาที่มี ตัวอย่างง่ายๆ:

struct foo {
    foo() {}
    foo( const foo & ) noexcept { std::cout << "copy\n"; }
    foo( foo && ) noexcept { std::cout << "move\n"; }
    ~foo() noexcept {}
};

int main() {
    std::vector< foo > v;
    for ( int i = 0; i < 3; ++i ) v.emplace_back();
}

สิ่งนี้ให้ผลลัพธ์ที่คาดหวัง:

move
move
move

แต่เมื่อฉันลบnoexceptจาก~foo()ผลที่ได้จะแตกต่างกัน

copy
copy
copy

ฉันเดาว่านี่ตอบคำถามนี้ด้วย


สำหรับฉันแล้วดูเหมือนว่าคำตอบอื่น ๆ จะพูดถึงตัวสร้างการเคลื่อนไหวเท่านั้นไม่ใช่เกี่ยวกับผู้ทำลายที่ต้องไม่มีข้อยกเว้น
Nikola Benes

มันควรจะเป็น แต่ปรากฎว่าใน gcc 4.7.2 ไม่ใช่ ดังนั้นปัญหานี้เป็นปัญหาเฉพาะสำหรับ gcc ควรได้รับการแก้ไขใน gcc 4.8.0 แม้ว่า ดูคำถาม StackOverflow ที่เกี่ยวข้อง
Nikola Benes

-1

ดูเหมือนว่าวิธีเดียว (สำหรับ C ++ 17 และก่อนหน้า) ในการบังคับstd::vectorใช้ use move semantics ในการจัดสรรใหม่คือการลบตัวสร้างการคัดลอก :) ด้วยวิธีนี้มันจะใช้ตัวสร้างการเคลื่อนไหวของคุณหรือพยายามตายในเวลารวบรวม :)

มีกฎระเบียบจำนวนมากที่มีstd::vectorไม่ต้องใช้คอนสตรัคย้ายจัดสรร แต่ไม่มีอะไรเกี่ยวกับการที่มันต้องใช้มัน

template<class T>
class move_only : public T{
public:
   move_only(){}
   move_only(const move_only&) = delete;
   move_only(move_only&&) noexcept {};
   ~move_only() noexcept {};

   using T::T;   
};

มีชีวิต

หรือ

template<class T>
struct move_only{
   T value;

   template<class Arg, class ...Args, typename = std::enable_if_t<
            !std::is_same_v<move_only<T>&&, Arg >
            && !std::is_same_v<const move_only<T>&, Arg >
    >>
   move_only(Arg&& arg, Args&&... args)
      :value(std::forward<Arg>(arg), std::forward<Args>(args)...)
   {}

   move_only(){}
   move_only(const move_only&) = delete;   
   move_only(move_only&& other) noexcept : value(std::move(other.value)) {};    
   ~move_only() noexcept {};   
};

รหัสสด

Tชั้นเรียนของคุณต้องมีตัวnoexceptดำเนินการสร้าง / ตัวดำเนินการย้ายและตัวnoexceptทำลาย มิฉะนั้นคุณจะได้รับข้อผิดพลาดในการคอมไพล์

std::vector<move_only<MyClass>> vec;

1
ไม่จำเป็นต้องลบตัวสร้างสำเนา หากตัวสร้างการย้ายไม่ใช่ข้อยกเว้นจะถูกใช้
balki

@balki อาจจะใช้ Standard ไม่จำเป็นต้องมีตอนนี้ นี่คือหัวข้อสนทนาgroups.google.com/a/isocpp.org/forum/…
tower120
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.