std :: vector คัดลอกวัตถุด้วย push_back หรือไม่?


169

หลังจากการสืบสวนกับ valgrind ฉันได้สรุปว่า std :: vector สร้างสำเนาของวัตถุที่คุณต้องการ push_back

เป็นเรื่องจริงเหรอ? เวกเตอร์ไม่สามารถเก็บการอ้างอิงหรือตัวชี้ของวัตถุโดยไม่มีการคัดลอก!

ขอบคุณ


20
นี่คือหลักการพื้นฐานของ C ++ วัตถุคือค่า การบ้านทำสำเนา ตัวแปรสองตัวที่อ้างถึงวัตถุเดียวกันนั้นเป็นไปไม่ได้นอกจากว่าคุณจะปรับเปลี่ยนชนิดที่มี*หรือ&เพื่อสร้างตัวชี้หรือการอ้างอิง
Daniel Earwicker

8
@DanielEarwicker push_back ใช้การอ้างอิงได้จริง ไม่ชัดเจนจากลายเซ็นเพียงอย่างเดียวว่าจะทำสำเนา
Brian Gordon

3
@BrianGordon - ไม่ได้บอกว่ามันเป็น! ดังนั้นความต้องการหลักการชี้นำ ดังนั้นแม้เราสามารถสรุปอะไรบางอย่างจากลายเซ็นของมันต้องใช้เวลาpush_back const&ไม่ว่าจะเป็นการโยนค่าออกไป (ไร้ประโยชน์) หรือมีวิธีการเรียกคืน ดังนั้นเรามองไปที่ลายเซ็นของbackและมันกลับมาธรรมดา&ดังนั้นทั้งค่าดั้งเดิมถูกคัดลอกหรือconstถูกทิ้งเงียบ ๆ (แย่มาก: พฤติกรรมที่อาจไม่ได้กำหนด) ดังนั้นสมมติว่านักออกแบบของvectorมีเหตุผล ( vector<bool>ไม่อดทน) เราสรุปว่ามันทำสำเนา
Daniel Earwicker

คำตอบ:


183

ใช่std::vector<T>::push_back()สร้างสำเนาของอาร์กิวเมนต์และเก็บไว้ในเวกเตอร์ หากคุณต้องการคำแนะนำการจัดเก็บวัตถุในเวกเตอร์ของคุณสร้างแทนstd::vector<whatever*>std::vector<whatever>

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


ฉันจะทราบด้วยว่าหากคุณใช้พอยน์เตอร์ดิบคุณจะต้องรับผิดชอบในการล้างข้อมูลหลังจากนั้น ไม่มีเหตุผลที่ดีที่จะทำสิ่งนี้ (ไม่ใช่ที่ฉันคิดได้) คุณควรใช้ตัวชี้สมาร์ทเสมอ
Ed S.

1
ที่กล่าวว่าคุณไม่ควรใช้ std :: auto_ptr ในคอนเทนเนอร์ stl สำหรับข้อมูลเพิ่มเติม: เหตุใดจึงใช้ผิด stdauto-ptr- กับคอนเทนเนอร์มาตรฐาน
OriginalCliche

24
ตั้งแต่ C ++ 11 push_backจะดำเนินการย้ายแทนการคัดลอกถ้าอาร์กิวเมนต์เป็นการอ้างอิงค่า rvalue (ออบเจ็กต์สามารถเปลี่ยนเป็นการอ้างอิงที่คุ้มค่าstd::move()ได้)
emlai

2
@tuple_cat ความคิดเห็นของคุณควรพูดว่า "ถ้าอาร์กิวเมนต์เป็นค่า rvalue" (ถ้าอาร์กิวเมนต์เป็นชื่อของเอนทิตีที่ประกาศเป็นการอ้างอิง rvalue ดังนั้นอาร์กิวเมนต์นั้นเป็น lvalue จริง ๆ และจะไม่ถูกย้ายจาก) - ตรวจสอบการแก้ไขของฉันเพื่อหาคำตอบของ "Karl Nicoll" ซึ่งทำผิดพลาดครั้งแรก
MM

มีคำตอบด้านล่าง แต่เพื่อให้ชัดเจน: ตั้งแต่ C ++ 11 ยังใช้emplace_backเพื่อหลีกเลี่ยงการคัดลอกหรือย้ายใด ๆ (สร้างวัตถุในสถานที่ให้บริการโดยภาชนะ)
Wormer

34

ใช่std::vectorเก็บสำเนา จะvectorรู้ได้อย่างไรว่าอายุการใช้งานของวัตถุของคุณเป็นเท่าไหร่?

หากคุณต้องการถ่ายโอนหรือแบ่งปันความเป็นเจ้าของวัตถุใช้ตัวชี้อาจเป็นตัวชี้สมาร์ทเช่นshared_ptr(พบในBoostหรือTR1 ) เพื่อความสะดวกในการจัดการทรัพยากร


3
เรียนรู้การใช้ shared_ptr - พวกเขาทำสิ่งที่คุณต้องการ สำนวนที่ฉันชอบคือ typedef boost :: shared_ptr <Foo> FooPtr; จากนั้นจัดทำตู้คอนเทนเนอร์ของ FooPtrs
pm100

3
@ pm100 - คุณรู้boost::ptr_vectorหรือไม่?
มานูเอล

2
ฉันยังชอบที่จะใช้เพียงแค่เขียนclass Foo { typedef boost::shared_ptr<Foo> ptr; }; Foo::ptr
รูเพิร์ตโจนส์

2
@ pm100 - shared_ptrไม่ได้ถูกไฟและลืม ดูstackoverflow.com/questions/327573และ stackoverflow.com/questions/701456
Daniel Earwicker

2
shared_ptr นั้นดีถ้าคุณมีความเป็นเจ้าของร่วมกัน แต่โดยทั่วไปแล้วจะใช้งานมากเกินไป unique_ptr หรือเพิ่ม scoped_ptr ทำให้รู้สึกมากขึ้นเมื่อความเป็นเจ้าของชัดเจน
Nemanja Trifunovic

28

ตั้งแต่ C ++ 11 เป็นต้นไปคอนเทนเนอร์มาตรฐานทั้งหมด ( std::vector, std::mapและอื่น ๆ ) รองรับซีแมนทิกส์ย้ายหมายถึงตอนนี้คุณสามารถส่งผ่านค่า rvalues ​​ไปยังคอนเทนเนอร์มาตรฐานและหลีกเลี่ยงการคัดลอก:

// Example object class.
class object
{
private:
    int             m_val1;
    std::string     m_val2;

public:
    // Constructor for object class.
    object(int val1, std::string &&val2) :
        m_val1(val1),
        m_val2(std::move(val2))
    {

    }
};

std::vector<object> myList;

// #1 Copy into the vector.
object foo1(1, "foo");
myList.push_back(foo1);

// #2 Move into the vector (no copy).
object foo2(1024, "bar");
myList.push_back(std::move(foo2));

// #3 Move temporary into vector (no copy).
myList.push_back(object(453, "baz"));

// #4 Create instance of object directly inside the vector (no copy, no move).
myList.emplace_back(453, "qux");

หรือคุณสามารถใช้พอยน์เตอร์อัจฉริยะต่าง ๆ เพื่อให้ได้ผลเหมือนกัน:

std::unique_ptr ตัวอย่าง

std::vector<std::unique_ptr<object>> myPtrList;

// #5a unique_ptr can only ever be moved.
auto pFoo = std::make_unique<object>(1, "foo");
myPtrList.push_back(std::move(pFoo));

// #5b unique_ptr can only ever be moved.
myPtrList.push_back(std::make_unique<object>(1, "foo"));

std::shared_ptr ตัวอย่าง

std::vector<std::shared_ptr<object>> objectPtrList2;

// #6 shared_ptr can be used to retain a copy of the pointer and update both the vector
// value and the local copy simultaneously.
auto pFooShared = std::make_shared<object>(1, "foo");
objectPtrList2.push_back(pFooShared);
// Pointer to object stored in the vector, but pFooShared is still valid.

2
โปรดทราบว่าstd::make_unique(น่ารำคาญ) มีเฉพาะใน C ++ 14 ขึ้นไป ตรวจสอบให้แน่ใจว่าคุณบอกคอมไพเลอร์ของคุณเพื่อตั้งค่าความสอดคล้องมาตรฐานหากคุณต้องการรวบรวมตัวอย่างเหล่านี้
Laryx Decidua

ใน 5a คุณสามารถใช้auto pFoo =เพื่อหลีกเลี่ยงการทำซ้ำ และstd::stringcasts ทั้งหมดสามารถลบออกได้ (มีการแปลงโดยปริยายจากตัวอักษรสตริงเป็นstd::string)
MM

2
@ user465139 make_uniqueสามารถนำไปใช้ใน C ++ 11 ได้อย่างง่ายดายดังนั้นจึงเป็นเรื่องน่ารำคาญเล็กน้อยสำหรับผู้ที่ติดกับคอมไพเลอร์ C ++ 11
MM

1
@MM: แน่นอน นี่คือการดำเนินการตามตำราเรียน:template<typename T, typename... Args> unique_ptr<T> make_unique(Args&&... args) { return unique_ptr<T>{new T{args...}}; }
Laryx Decidua

1
@Anakin - ใช่พวกเขาควรทำ แต่ถ้าคุณคัดลอก หากคุณใช้std::move()กับstd::shared_ptrตัวชี้ที่ใช้ร่วมกันเดิมอาจมีการเปลี่ยนแปลงตัวชี้เนื่องจากความเป็นเจ้าของถูกส่งผ่านไปยังเวกเตอร์ ดูที่นี่: coliru.stacked-crooked.com/a/99d4f04f05e5c7f3
Karl Nicoll

15

std :: vector สร้างสำเนาของสิ่งที่ถูกเก็บไว้ใน vector เสมอ

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


3
มันไม่ได้ขึ้นอยู่กับประเภท มันมักจะทำสำเนา หากตัวชี้ทำสำเนาของตัวชี้
pm100

คุณทั้งคู่พูดถูก ในทางเทคนิคแล้วใช่มันจะทำสำเนาเสมอ ในทางปฏิบัติหากคุณส่งตัวชี้ไปยังวัตถุมันจะคัดลอกตัวชี้ไม่ใช่วัตถุ อย่างปลอดภัยคุณควรใช้ตัวชี้สมาร์ทที่เหมาะสม
Steven Sudit

1
ใช่มันเป็นการคัดลอกเสมอ - อย่างไรก็ตาม "วัตถุ" ที่ OP อ้างถึงน่าจะเป็นคลาสหรือโครงสร้างดังนั้นฉันจึงอ้างอิงว่าการคัดลอก "วัตถุ" นั้นขึ้นอยู่กับคำจำกัดความ แม้ว่าจะพูดไม่ดี
Reed Copsey

3

std :: vector ไม่เพียง แต่ทำสำเนาสิ่งที่คุณกดกลับ แต่คำจำกัดความของคอลเลกชันระบุว่าจะทำเช่นนั้นและคุณไม่สามารถใช้วัตถุโดยปราศจากซีแมนทิกส์ที่ถูกต้องภายในเวกเตอร์ ตัวอย่างเช่นคุณไม่ได้ใช้ auto_ptr ในเวกเตอร์


2

เกี่ยวข้องใน C ++ 11 เป็นemplaceตระกูลของฟังก์ชันสมาชิกที่ให้คุณถ่ายโอนความเป็นเจ้าของวัตถุโดยย้ายไปไว้ในคอนเทนเนอร์

สำนวนการใช้งานจะเป็นอย่างไร

std::vector<Object> objs;

Object l_value_obj { /* initialize */ };
// use object here...

objs.emplace_back(std::move(l_value_obj));

การย้ายสำหรับวัตถุ lvalue มีความสำคัญไม่เช่นนั้นมันจะถูกส่งต่อเป็นการอ้างอิงหรือการอ้างอิง const และตัวสร้างการย้ายจะไม่ถูกเรียก


0

หากคุณไม่ต้องการสำเนา วิธีที่ดีที่สุดคือการใช้ตัวชี้แบบเวกเตอร์ (หรือโครงสร้างอื่นที่ให้บริการสำหรับเป้าหมายเดียวกัน) ถ้าคุณต้องการสำเนา; ใช้โดยตรง push_back () คุณไม่มีทางเลือกอื่น


1
หมายเหตุเกี่ยวกับตัวชี้เวกเตอร์: vector <shared_ptr <obj>> นั้นปลอดภัยกว่า vector <obj *> และ shared_ptr เป็นส่วนหนึ่งของมาตรฐานเมื่อปีที่แล้ว
rich.e

-1

ทำไมการสืบสวน valgrind หลายครั้งถึงทำแบบนี้! เพียงพิสูจน์ด้วยตัวคุณเองด้วยรหัสง่ายๆเช่น

std::vector<std::string> vec;

{
      std::string obj("hello world");
      vec.push_pack(obj);
}

std::cout << vec[0] << std::endl;  

หากพิมพ์ "hello world" วัตถุจะต้องถูกคัดลอก


4
นี่ไม่ได้เป็นการพิสูจน์ หากวัตถุไม่ได้ถูกคัดลอกคำสั่งสุดท้ายของคุณจะเป็นพฤติกรรมที่ไม่ได้กำหนดและสามารถพิมพ์สวัสดี
Mat

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