การ push_back องค์ประกอบจากเวกเตอร์เดียวกันปลอดภัยหรือไม่


126
vector<int> v;
v.push_back(1);
v.push_back(v[0]);

หาก push_back ครั้งที่สองทำให้เกิดการจัดสรรใหม่การอ้างอิงถึงจำนวนเต็มแรกในเวกเตอร์จะใช้ไม่ได้อีกต่อไป อย่างนี้ไม่ปลอดภัยเหรอ?

vector<int> v;
v.push_back(1);
v.reserve(v.size() + 1);
v.push_back(v[0]);

สิ่งนี้ทำให้ปลอดภัย?


4
หมายเหตุ: ขณะนี้มีการอภิปรายในฟอรัมข้อเสนอมาตรฐาน ในฐานะที่เป็นส่วนหนึ่งของมันบางคนให้การดำเนินการเป็นตัวอย่างของ push_backผู้โพสต์อีกคนสังเกตเห็นข้อบกพร่องในนั้นซึ่งไม่สามารถจัดการกับกรณีที่คุณอธิบายได้อย่างถูกต้อง เท่าที่ฉันสามารถบอกได้แย้งว่านี่ไม่ใช่ข้อบกพร่อง ไม่ได้บอกว่าเป็นข้อพิสูจน์ที่สรุปได้เป็นเพียงข้อสังเกต
Benjamin Lindley

9
ฉันขอโทษ แต่ฉันไม่รู้ว่าจะยอมรับคำตอบใดเนื่องจากยังคงมีข้อโต้แย้งเกี่ยวกับคำตอบที่ถูกต้อง
Neil Kirk

4
ฉันถูกขอให้แสดงความคิดเห็นในคำถามนี้โดยความคิดเห็นที่ 5 ภายใต้: stackoverflow.com/a/18647445/576911 ฉันกำลังทำเช่นนั้นโดยการโหวตทุกคำตอบที่กล่าวว่าในปัจจุบัน: ใช่มันปลอดภัยที่จะ push_back องค์ประกอบจากเวกเตอร์เดียวกัน
Howard Hinnant

2
@BenVoigt: <shrug> หากคุณไม่เห็นด้วยกับสิ่งที่มาตรฐานกล่าวหรือแม้ว่าคุณจะเห็นด้วยกับมาตรฐาน แต่ไม่คิดว่าจะระบุชัดเจนเพียงพอนี่เป็นทางเลือกสำหรับคุณเสมอ : cplusplus.github.io/LWG/ lwg-active.html # submit_issue ฉันใช้ตัวเลือกนี้มากกว่าที่จำได้ บางครั้งประสบความสำเร็จบางครั้งก็ไม่ได้ หากคุณต้องการถกเถียงว่ามาตรฐานกล่าวถึงอะไรหรือควรพูดอย่างไร SO ไม่ใช่ฟอรัมที่มีประสิทธิภาพ บทสนทนาของเราไม่มีความหมายเชิงบรรทัดฐาน แต่คุณสามารถมีโอกาสที่จะสร้างผลกระทบเชิงบรรทัดฐานได้โดยไปที่ลิงก์ด้านบน
Howard Hinnant

2
@ Polaris878 หาก push_back ทำให้เวกเตอร์ถึงขีดความสามารถเวกเตอร์จะจัดสรรบัฟเฟอร์ใหม่ที่ใหญ่กว่าคัดลอกข้อมูลเก่าจากนั้นจึงลบบัฟเฟอร์เก่า จากนั้นจะแทรกองค์ประกอบใหม่ ปัญหาคือองค์ประกอบใหม่เป็นการอ้างอิงข้อมูลในบัฟเฟอร์เก่าซึ่งเพิ่งถูกลบไป เว้นแต่ว่า push_back จะทำสำเนาของค่าก่อนที่จะลบจะเป็นการอ้างอิงที่ไม่ถูกต้อง
Neil Kirk

คำตอบ:


31

ดูเหมือนว่าhttp://www.open-std.org/jtc1/sc22/wg21/docs/lwg-closed.html#526 ได้แก้ไขปัญหานี้ (หรือสิ่งที่คล้ายกันมาก) ว่าเป็นข้อบกพร่องที่อาจเกิดขึ้นในมาตรฐาน:

1) พารามิเตอร์ที่ใช้โดยการอ้างอิง const สามารถเปลี่ยนแปลงได้ในระหว่างการดำเนินการของฟังก์ชัน

ตัวอย่าง:

ให้ std :: vector v:

v.insert (v.begin (), v [2]);

v [2] สามารถเปลี่ยนแปลงได้โดยการย้ายองค์ประกอบของเวกเตอร์

มติที่เสนอคือว่านี่ไม่ใช่ข้อบกพร่อง:

vector :: insert (iter, value) เป็นสิ่งจำเป็นในการทำงานเนื่องจากมาตรฐานไม่ได้ให้สิทธิ์ในการไม่ทำงาน


ฉันพบการอนุญาตใน 17.6.4.9: "ถ้าอาร์กิวเมนต์ของฟังก์ชันมีค่าที่ไม่ถูกต้อง (เช่นค่าภายนอกโดเมนของฟังก์ชันหรือตัวชี้ไม่ถูกต้องสำหรับการใช้งานที่ตั้งใจไว้) พฤติกรรมจะไม่ถูกกำหนด" หากการจัดสรรใหม่เกิดขึ้นตัวทำซ้ำและการอ้างอิงถึงองค์ประกอบทั้งหมดจะไม่ถูกต้องหมายความว่าการอ้างอิงพารามิเตอร์ที่ส่งไปยังฟังก์ชันจะไม่ถูกต้องเช่นกัน
Ben Voigt

4
ฉันคิดว่าประเด็นคือการใช้งานมีหน้าที่รับผิดชอบในการจัดสรรใหม่ เป็นหน้าที่ของมันเพื่อให้แน่ใจว่าพฤติกรรมถูกกำหนดหากอินพุตถูกกำหนดไว้ในตอนแรก เนื่องจากข้อกำหนดระบุไว้อย่างชัดเจนว่า push_back ทำสำเนาการใช้งานอาจต้องเสียเวลาดำเนินการแคชหรือคัดลอกค่าทั้งหมดก่อนที่จะยกเลิกการจัดสรร เนื่องจากในคำถามนี้ไม่มีการอ้างอิงภายนอกเหลืออยู่จึงไม่สำคัญว่าตัวทำซ้ำและการอ้างอิงจะไม่ถูกต้องหรือไม่
OlivierD

3
@NeilKirk ฉันคิดว่านี่น่าจะเป็นคำตอบของนักเขียนมันยังกล่าวถึงโดย Stephan T. Lavavej ใน Redditโดยใช้อาร์กิวเมนต์เดียวกันเป็นหลัก
TemplateRex

v.insert(v.begin(), v[2]);ไม่สามารถทริกเกอร์การจัดสรรใหม่ได้ แล้วสิ่งนี้จะตอบคำถามได้อย่างไร?
ThomasMcLeod

@ThomasMcLeod: ใช่มันสามารถกระตุ้นการจัดสรรใหม่ได้อย่างชัดเจน คุณกำลังขยายขนาดของเวกเตอร์โดยการแทรกองค์ประกอบใหม่
ยีราฟสีม่วง

21

ใช่มันปลอดภัยและการใช้งานไลบรารีมาตรฐานจะข้ามห่วงเพื่อให้เป็นเช่นนั้น

ฉันเชื่อว่าผู้ดำเนินการติดตามข้อกำหนดนี้กลับไปที่ 23.2 / 11 อย่างใด แต่ฉันไม่สามารถหาวิธีการได้และฉันไม่พบสิ่งที่เป็นรูปธรรมมากขึ้นเช่นกัน สิ่งที่ดีที่สุดที่ฉันสามารถหาได้คือบทความนี้:

http://www.drdobbs.com/cpp/copying-container-elements-from-the-c-li/240155771

การตรวจสอบการใช้งานของ libc ++ และ libstdc ++ แสดงให้เห็นว่าปลอดภัยเช่นกัน


9
การสนับสนุนบางอย่างจะช่วยได้มาก
chris

3
เป็นเรื่องที่น่าสนใจฉันต้องยอมรับว่าฉันไม่เคยพิจารณาคดีนี้มาก่อน แต่ดูเหมือนว่าจะประสบความสำเร็จค่อนข้างยาก ยังมีไว้เพื่อvec.insert(vec.end(), vec.begin(), vec.end());?
Matthieu M.

2
@MatthieuM ไม่: ตารางที่ 100 ระบุว่า: "pre: i และ j ไม่ใช่ตัววนซ้ำใน a"
Sebastian Redl

2
ตอนนี้ฉันกำลังโหวตเพราะนี่คือความทรงจำของฉันเช่นกัน แต่จำเป็นต้องมีการอ้างอิง
bames53

3
คือ 23.2 / 11 ในเวอร์ชันที่คุณกำลังใช้ "เว้นแต่จะระบุไว้เป็นอย่างอื่น (ไม่ว่าจะโดยชัดแจ้งหรือโดยการกำหนดฟังก์ชันในรูปของฟังก์ชันอื่น ๆ ) การเรียกใช้ฟังก์ชันสมาชิกคอนเทนเนอร์หรือส่งคอนเทนเนอร์เป็นอาร์กิวเมนต์ไปยังฟังก์ชันไลบรารีจะต้องไม่ทำให้ตัวทำซ้ำเป็นโมฆะ เป็นหรือเปลี่ยนค่าของวัตถุภายในคอนเทนเนอร์นั้น " ? แต่vector.push_backระบุไว้เป็นอย่างอื่น "ทำให้เกิดการจัดสรรใหม่หากขนาดใหม่มากกว่าความจุเก่า" และ (ที่reserve) "การจัดสรรใหม่ทำให้การอ้างอิงตัวชี้และตัวทำซ้ำทั้งหมดที่อ้างถึงองค์ประกอบในลำดับไม่ถูกต้อง"
Ben Voigt

13

มาตรฐานรับประกันแม้แต่ตัวอย่างแรกของคุณว่าปลอดภัย การอ้างอิง C ++ 11

[sequence.reqmts]

3 ในตาราง 100 และ 101 ... Xหมายถึงคลาสคอนเทนเนอร์ลำดับaหมายถึงค่าของXองค์ประกอบที่มีประเภทT... tหมายถึง lvalue หรือค่า const r ของX::value_type

16 ตาราง 101 ...

การแสดงออก a.push_back(t) ประเภทกลับ void ความหมายในการดำเนินงานผนวกสำเนาt. ต้อง: Tจะต้องเข้า CopyInsertable ตู้คอนเทนเนอร์ , , ,X basic_stringdequelistvector

ดังนั้นแม้ว่าจะไม่ใช่เรื่องเล็กน้อย แต่การใช้งานจะต้องรับประกันว่าจะไม่ทำให้การอ้างอิงเป็นโมฆะเมื่อทำไฟล์push_back.


7
ฉันไม่เห็นว่าสิ่งนี้รับประกันได้อย่างไรว่าจะปลอดภัย
jrok

4
@Angew: มันไม่ถูกต้องอย่างแน่นอนtคำถามเดียวคือก่อนหรือหลังทำสำเนา ประโยคสุดท้ายของคุณผิดอย่างแน่นอน
Ben Voigt

4
@BenVoigt เนื่องจากtตรงตามเงื่อนไขเบื้องต้นที่ระบุไว้จึงรับประกันพฤติกรรมที่อธิบายไว้ ไม่อนุญาตให้นำไปใช้เพื่อทำให้เงื่อนไขเบื้องต้นเป็นโมฆะแล้วใช้เป็นข้ออ้างที่จะไม่ปฏิบัติตามที่ระบุไว้
bames53

8
@BenVoigt ลูกค้าไม่จำเป็นต้องรักษาเงื่อนไขเบื้องต้นตลอดการโทร; เพื่อให้แน่ใจว่าตรงตามจุดเริ่มต้นของการโทรเท่านั้น
bames53

6
@BenVoigt นั่นเป็นจุดที่ดี แต่ฉันเชื่อว่ามี functor ที่ส่งไปยังfor_eachไม่จำเป็นต้องทำให้ตัววนซ้ำเป็นโมฆะ ฉันไม่สามารถหาข้อมูลอ้างอิงได้for_eachแต่ฉันเห็นข้อความของอัลกอริทึมบางอย่างเช่น "op และ binary_op จะไม่ทำให้ตัววนซ้ำหรือส่วนย่อยเป็นโมฆะ"
bames53

7

ไม่ชัดเจนว่าตัวอย่างแรกนั้นปลอดภัยเนื่องจากการใช้งานที่ง่ายที่สุดคือการpush_backจัดสรรเวกเตอร์ใหม่ก่อนหากจำเป็นจากนั้นจึงคัดลอกข้อมูลอ้างอิง

แต่อย่างน้อยก็ดูเหมือนว่าจะปลอดภัยกับ Visual Studio 2010 การใช้งานpush_backจะจัดการกรณีพิเศษเมื่อคุณดันองค์ประกอบในเวกเตอร์กลับ รหัสมีโครงสร้างดังนี้:

void push_back(const _Ty& _Val)
    {   // insert element at end
    if (_Inside(_STD addressof(_Val)))
        {   // push back an element
                    ...
        }
    else
        {   // push back a non-element
                    ...
        }
    }

8
อยากทราบว่าสเปกต้องแบบนี้ถึงจะปลอดภัยไหม
Nawaz

1
ตามมาตรฐานไม่จำเป็นต้องปลอดภัย อย่างไรก็ตามเป็นไปได้ที่จะนำไปใช้อย่างปลอดภัย
Ben Voigt

2
@BenVoigt ผมว่ามันถูกต้องเพื่อความปลอดภัย (ดูคำตอบของฉัน)
Angew ไม่ภูมิใจอีกต่อไปเมื่อ

2
@BenVoigt ในเวลาที่คุณส่งผ่านข้อมูลอ้างอิงนั้นถูกต้อง
Angew ไม่ภูมิใจใน SO

4
@ Angew: นั่นยังไม่เพียงพอ คุณต้องส่งข้อมูลอ้างอิงที่ใช้งานได้ตลอดระยะเวลาการโทรและข้อมูลอ้างอิงนี้ใช้ไม่ได้
Ben Voigt

3

นี้ไม่ได้รับประกันจากมาตรฐาน แต่เป็นจุดข้อมูลอื่นv.push_back(v[0])มีความปลอดภัยสำหรับlibc LLVM ของ ++

libc ++ 'sstd::vector::push_backสาย__push_back_slow_pathเมื่อต้องการหน่วยความจำจัดสรร:

void __push_back_slow_path(_Up& __x) {
  allocator_type& __a = this->__alloc();
  __split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1), 
                                                  size(), 
                                                  __a);
  // Note that we construct a copy of __x before deallocating
  // the existing storage or moving existing elements.
  __alloc_traits::construct(__a, 
                            _VSTD::__to_raw_pointer(__v.__end_), 
                            _VSTD::forward<_Up>(__x));
  __v.__end_++;
  // Moving existing elements happens here:
  __swap_out_circular_buffer(__v);
  // When __v goes out of scope, __x will be invalid.
}

สำเนาไม่เพียง แต่ต้องทำก่อนที่จะยกเลิกการจัดสรรที่เก็บข้อมูลที่มีอยู่ แต่ก่อนที่จะย้ายจากองค์ประกอบที่มีอยู่ ฉันคิดว่าการเคลื่อนย้ายองค์ประกอบที่มีอยู่เสร็จสิ้น__swap_out_circular_bufferแล้วซึ่งในกรณีนี้การใช้งานนี้จะปลอดภัยอย่างแน่นอน
Ben Voigt

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

1

เวอร์ชันแรกไม่ปลอดภัยอย่างแน่นอน:

การดำเนินการกับตัวทำซ้ำที่ได้รับจากการเรียกใช้คอนเทนเนอร์ไลบรารีมาตรฐานหรือฟังก์ชันสมาชิกสตริงอาจเข้าถึงคอนเทนเนอร์ที่อยู่ภายใต้ แต่จะไม่แก้ไข [หมายเหตุ: โดยเฉพาะอย่างยิ่งการดำเนินการกับคอนเทนเนอร์ที่ทำให้ตัววนซ้ำไม่ถูกต้องขัดแย้งกับการดำเนินการบนตัวทำซ้ำที่เกี่ยวข้องกับคอนเทนเนอร์นั้น - หมายเหตุ]

จากหัวข้อ 17.6.5.9


โปรดทราบว่านี่คือส่วนเกี่ยวกับการแข่งขันข้อมูลซึ่งโดยปกติผู้คนมักคิดร่วมกับเธรด ... แต่คำจำกัดความที่แท้จริงเกี่ยวข้องกับความสัมพันธ์ "เกิดขึ้นก่อน" และฉันไม่เห็นความสัมพันธ์ลำดับใด ๆ ระหว่างผลข้างเคียงหลายประการของpush_backใน เล่นที่นี่กล่าวคือดูเหมือนว่าการอ้างอิงที่ไม่ถูกต้องดูเหมือนจะไม่ถูกกำหนดตามลำดับที่เกี่ยวกับการคัดลอก - สร้างองค์ประกอบส่วนท้ายใหม่


1
ควรเข้าใจว่านั่นคือบันทึกไม่ใช่กฎดังนั้นจึงอธิบายถึงผลลัพธ์ของกฎก่อนหน้านี้ ... และผลที่ตามมาก็เหมือนกันสำหรับการอ้างอิง
Ben Voigt

5
ผลลัพธ์ของv[0]ไม่ใช่ตัววนซ้ำpush_back()ไม่ใช้ตัววนซ้ำเช่นเดียวกัน ดังนั้นจากมุมมองของนักกฎหมายด้านภาษาข้อโต้แย้งของคุณถือเป็นโมฆะ ขอโทษ ฉันรู้ว่าตัววนซ้ำส่วนใหญ่เป็นตัวชี้และจุดที่ทำให้ตัววนซ้ำเป็นโมฆะนั้นค่อนข้างเหมือนกับการอ้างอิง แต่ส่วนหนึ่งของมาตรฐานที่คุณอ้างถึงนั้นไม่เกี่ยวข้องกับสถานการณ์ในมือ
cmaster - คืนสถานะ monica

-1 มันเป็นคำพูดที่ไม่เกี่ยวข้องโดยสิ้นเชิงและไม่ได้คำตอบอยู่ดี คณะกรรมการระบุว่าx.push_back(x[0])ปลอดภัย
Nawaz

0

ปลอดภัยอย่างสมบูรณ์

ในตัวอย่างที่สองของคุณคุณมี

v.reserve(v.size() + 1);

ซึ่งไม่จำเป็นเพราะถ้าเวกเตอร์มีขนาดเกินมันจะบ่งบอกถึงreserve.

Vector มีหน้าที่รับผิดชอบต่อสิ่งนี้ไม่ใช่คุณ


-1

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

ส่วน 23.2.1 ข้อกำหนดทั่วไปเกี่ยวกับคอนเทนเนอร์

16
  • a.push_back (t) ต่อท้ายสำเนาของ t ต้องการ: T จะต้อง CopyInsertable ใน X
  • a.push_back (rv) ต่อท้ายสำเนาของ rv ต้องการ: T จะถูก MoveInsertable เป็น X

การดำเนินการ push_back จึงต้องแน่ใจว่ามีการ v[0]แทรกสำเนา โดยตัวอย่างการโต้แย้งสมมติว่ามีการใช้งานที่จะจัดสรรใหม่ก่อนที่จะคัดลอกจะไม่มีการต่อท้ายสำเนาแน่นอนv[0]และเป็นการละเมิดข้อกำหนด


2
push_backอย่างไรก็ตามจะปรับขนาดเวกเตอร์ด้วยและในการนำไปใช้อย่างไร้เดียงสาสิ่งนี้จะทำให้การอ้างอิงเป็นโมฆะก่อนที่จะเกิดการคัดลอก ดังนั้นหากคุณไม่สามารถสำรองข้อมูลนี้ได้โดยการอ้างอิงจากมาตรฐานฉันจะถือว่าผิด
Konrad Rudolph

4
โดย "this" คุณหมายถึงตัวอย่างแรกหรือที่สอง? push_backจะคัดลอกค่าลงในเวกเตอร์ แต่ (เท่าที่ฉันเห็น) ที่อาจเกิดขึ้นหลังจากการจัดสรรใหม่ ณ จุดนั้นการอ้างอิงที่พยายามคัดลอกไม่ถูกต้องอีกต่อไป
Mike Seymour

1
push_backได้รับการโต้แย้งโดยการอ้างอิง
bames53

1
@OlivierD: มันจะต้อง (1) จัดสรรพื้นที่ใหม่ (2) คัดลอกองค์ประกอบใหม่ (3) ย้าย - สร้างองค์ประกอบที่มีอยู่ (4) ทำลายองค์ประกอบที่ย้ายจาก (5) ปลดปล่อยที่เก็บข้อมูลเก่า - ตามลำดับนั้น - เพื่อให้เวอร์ชันแรกใช้งานได้
Ben Voigt

1
@BenVoigt ทำไมคอนเทนเนอร์อื่นจึงต้องการให้ประเภท CopyInsertable ถ้ามันจะละเว้นคุณสมบัตินั้นโดยสิ้นเชิง
OlivierD

-2

ตั้งแต่ 23.3.6.5/1: Causes reallocation if the new size is greater than the old capacity. If no reallocation happens, all the iterators and references before the insertion point remain valid.

เนื่องจากเรากำลังแทรกในตอนท้ายจึงไม่มีการอ้างอิงใด ๆ ที่จะไม่ถูกต้องหากเวกเตอร์ไม่ได้ปรับขนาด ดังนั้นถ้าเวกเตอร์เป็นcapacity() > size()ก็รับประกันว่าจะใช้งานได้มิฉะนั้นจะรับประกันได้ว่าเป็นพฤติกรรมที่ไม่ได้กำหนดไว้


ฉันเชื่อว่าสเปคจริงรับรองว่าจะใช้งานได้ในทั้งสองกรณี ฉันกำลังรอข้อมูลอ้างอิงอยู่
bames53

ไม่มีการกล่าวถึงตัวทำซ้ำหรือความปลอดภัยของตัวทำซ้ำในคำถาม
OlivierD

3
@OlivierD ส่วนวนซ้ำนั้นไม่จำเป็นที่นี่: ฉันสนใจในreferencesส่วนของคำพูด
Mark B

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