มีการใช้งาน unique_ptr กับอาเรย์ไหม?


238

std::unique_ptr มีการสนับสนุนสำหรับอาร์เรย์ตัวอย่างเช่น:

std::unique_ptr<int[]> p(new int[10]);

แต่มันจำเป็นหรือไม่ อาจจะมีความสะดวกมากขึ้นในการใช้หรือstd::vectorstd::array

คุณพบว่ามีประโยชน์สำหรับการสร้างที่?


6
เพื่อความสมบูรณ์ฉันควรชี้ให้เห็นว่าไม่มีstd::shared_ptr<T[]>แต่ควรจะมีและอาจจะอยู่ใน C ++ 14 หากใครก็ตามไม่ใส่ใจที่จะเขียนข้อเสนอ boost::shared_arrayในเวลาเฉลี่ยที่มีเสมอ
นามแฝง

13
std::shared_ptr<T []> อยู่ใน c ++ 17 ตอนนี้
陳力

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

คำตอบ:


256

บางคนไม่มีความหรูหราในการใช้std::vectorงานแม้แต่กับผู้จัดสรร บางคนต้องการอาร์เรย์ที่มีขนาดแบบไดนามิกดังนั้นจึงstd::arrayออก และบางคนได้รับอาร์เรย์จากรหัสอื่นที่ทราบว่าส่งคืนอาร์เรย์ และรหัสนั้นจะไม่ถูกเขียนใหม่เพื่อส่งคืนvectorหรือบางสิ่ง

โดยการอนุญาตให้unique_ptr<T[]>คุณบริการความต้องการเหล่านั้น

ในระยะสั้นที่คุณใช้unique_ptr<T[]>เมื่อคุณต้องการที่จะ เมื่อทางเลือกอื่นไม่ได้ผลสำหรับคุณ มันเป็นเครื่องมือสุดท้าย


27
@NoSenseEtAl: ฉันไม่แน่ใจว่าส่วนหนึ่งของ "บางคนไม่ได้รับอนุญาตให้ทำเช่นนั้น" จะนำพาคุณ บางโครงการมีข้อกำหนดเฉพาะและในหมู่พวกเขาอาจเป็น "คุณไม่ได้ใช้vector" คุณสามารถโต้แย้งได้ว่าสิ่งเหล่านั้นเป็นข้อกำหนดที่สมเหตุสมผลหรือไม่ แต่คุณไม่สามารถปฏิเสธได้ว่ามันมีอยู่จริงหรือไม่
Nicol Bolas

21
มีเหตุผลใดในโลกทำไมคนจะไม่สามารถที่จะใช้คือถ้าพวกเขาสามารถใช้std::vector std::unique_ptr
Miles Rout

66
นี่คือเหตุผลที่จะไม่ใช้เวกเตอร์: sizeof (std :: vector <char>) == 24; sizeof (std :: unique_ptr <char []>) == 8
Arvid

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

16
@DanNissenbaum นอกจากนี้ยังมีระบบเรียลไทม์ที่ยากบางอย่างที่ไม่ได้รับอนุญาตให้ใช้การจัดสรรหน่วยความจำแบบไดนามิกเลยเนื่องจากความล่าช้าในการทำให้เกิดการเรียกระบบอาจไม่ถูก จำกัด ขอบเขตในทางทฤษฎีและคุณไม่สามารถพิสูจน์พฤติกรรมตามเวลาจริงของโปรแกรม หรือขอบเขตอาจใหญ่เกินไปซึ่งทำให้ขีด จำกัด WCET ของคุณ แม้ว่าจะไม่สามารถใช้งานได้ที่นี่เนื่องจากพวกเขาจะไม่ใช้unique_ptrอย่างใดอย่างหนึ่ง แต่โครงการเหล่านั้นมีอยู่จริง
Emily L.

124

มีการแลกเปลี่ยนและคุณเลือกโซลูชันที่ตรงกับสิ่งที่คุณต้องการ ปิดส่วนหัวของฉัน:

ขนาดเริ่มต้น

  • vectorและunique_ptr<T[]>อนุญาตให้มีการระบุขนาดในเวลาทำงาน
  • array อนุญาตเฉพาะขนาดที่ระบุในเวลารวบรวม

ปรับขนาด

  • arrayและunique_ptr<T[]>ไม่อนุญาตให้ปรับขนาด
  • vector ทำ

การเก็บรักษา

  • vectorและunique_ptr<T[]>เก็บข้อมูลไว้นอกวัตถุ (โดยทั่วไปจะอยู่บนฮีป)
  • array เก็บข้อมูลโดยตรงในวัตถุ

คัดลอก

  • arrayและvectorอนุญาตให้คัดลอก
  • unique_ptr<T[]> ไม่อนุญาตให้คัดลอก

Swap / ย้าย

  • vectorและunique_ptr<T[]>มีเวลา O (1) swapและดำเนินการย้าย
  • arrayมีเวลา O (n) swapและการดำเนินการย้ายโดยที่ n คือจำนวนขององค์ประกอบในอาร์เรย์

ตัวชี้ / การอ้างอิง / การวนซ้ำไม่ถูกต้อง

  • array ทำให้มั่นใจในพอยน์เตอร์การอ้างอิงและตัววนซ้ำจะไม่ถูกยกเลิกในขณะที่วัตถุยังมีชีวิต swap()
  • unique_ptr<T[]>ไม่มีตัววนซ้ำ พอยน์เตอร์และการอ้างอิงจะใช้งานไม่ได้swap()ในขณะที่วัตถุยังมีชีวิตอยู่ (หลังจากทำการสลับพอยน์เตอร์พอยน์เตอร์จะชี้ไปยังอาร์เรย์ที่คุณสลับกับดังนั้นพวกเขาจึงยังคง "ใช้ได้" ในแง่นั้น)
  • vector อาจทำให้ตัวชี้การอ้างอิงและตัววนซ้ำในการจัดสรรคืนใด ๆ (และให้การรับประกันบางอย่างว่าการจัดสรรใหม่สามารถเกิดขึ้นได้ในการดำเนินการบางอย่างเท่านั้น)

ความเข้ากันได้กับแนวคิดและอัลกอริทึม

  • arrayและvectorเป็นภาชนะบรรจุทั้งสอง
  • unique_ptr<T[]> ไม่ใช่คอนเทนเนอร์

ฉันต้องยอมรับนี่เป็นโอกาสสำหรับการปรับโครงสร้างบางอย่างด้วยการออกแบบตามนโยบาย


1
ฉันไม่แน่ใจว่าฉันเข้าใจสิ่งที่คุณหมายถึงในบริบทของการทำให้ตัวชี้ใช้ไม่ได้ สิ่งนี้เกี่ยวกับพอยน์เตอร์ไปยังวัตถุด้วยตนเองหรือเป็นพอยน์เตอร์ไปยังองค์ประกอบหรือไม่? หรืออย่างอื่น? การรับประกันแบบไหนที่คุณได้รับจากอาร์เรย์ที่คุณไม่ได้รับจากเวกเตอร์
jogojapan

3
สมมติว่าคุณมี iterator vectorชี้หรืออ้างอิงถึงองค์ประกอบของหนึ่ง จากนั้นคุณเพิ่มขนาดหรือความจุของสิ่งvectorนั้นซึ่งจะบังคับให้มีการจัดสรรใหม่ แล้วที่ iterator vectorชี้หรือการอ้างอิงจุดไม่มีอีกต่อไปที่องค์ประกอบของที่ นี่คือสิ่งที่เราหมายถึงโดย "การทำให้เป็นโมฆะ" ปัญหานี้ไม่ได้เกิดขึ้นarrayเพราะไม่มี "การจัดสรรใหม่" จริงๆแล้วฉันเพิ่งสังเกตรายละเอียดและฉันก็แก้ไขให้เหมาะสม
นามแฝง

1
ตกลงไม่สามารถใช้การไม่ได้เนื่องจากการจัดสรรใหม่ในอาร์เรย์หรือunique_ptr<T[]>เนื่องจากไม่มีการจัดสรรใหม่ แต่แน่นอนเมื่ออาร์เรย์ไม่อยู่ในขอบเขตพอยน์เตอร์ไปยังองค์ประกอบเฉพาะจะยังคงใช้งานไม่ได้
jogojapan

ใช่การเดิมพันทั้งหมดจะปิดถ้าวัตถุไม่อยู่อีกต่อไป
นามแฝง

1
@rubenvb แน่นอนว่าคุณทำได้ แต่คุณไม่สามารถ (พูด) ใช้ range-based สำหรับลูปโดยตรง อนึ่งแตกต่างจากปกติT[]ขนาด (หรือข้อมูลที่เทียบเท่า) จะต้องแขวนอยู่รอบ ๆoperator delete[]เพื่อทำลายองค์ประกอบของอาร์เรย์อย่างถูกต้อง มันคงจะดีถ้าโปรแกรมเมอร์เข้าถึงมัน
นามแฝง

73

เหตุผลหนึ่งที่คุณอาจใช้ a unique_ptrคือถ้าคุณไม่ต้องการจ่ายค่ารันไทม์ของการกำหนดค่าเริ่มต้นของอาร์เรย์

std::vector<char> vec(1000000); // allocates AND value-initializes 1000000 chars

std::unique_ptr<char[]> p(new char[1000000]); // allocates storage for 1000000 chars

ตัวstd::vectorสร้างและstd::vector::resize()จะกำหนดค่าเริ่มต้นT- แต่newจะไม่ทำเช่นนั้นหากTเป็น POD

ดูค่าวัตถุที่เริ่มต้นใน C ++ 11 และ std :: vector constructor

โปรดทราบว่าvector::reserveนี่ไม่ใช่ทางเลือกอื่นที่นี่: การเข้าถึงตัวชี้ raw หลังจาก std :: vector :: Reserve safe หรือไม่

มันเป็นเหตุผลเดียวกันโปรแกรมเมอร์ C อาจเลือกมากกว่าmalloccalloc


แต่ด้วยเหตุผลนี้คือไม่ได้เป็นเพียงการแก้ปัญหา
Ruslan

@Ruslan ในโซลูชันที่เชื่อมโยงองค์ประกอบของอาร์เรย์แบบไดนามิกยังคงเป็นค่าเริ่มต้น แต่การกำหนดค่าเริ่มต้นไม่ได้ทำอะไรเลย ฉันยอมรับว่าเครื่องมือเพิ่มประสิทธิภาพที่ล้มเหลวที่จะตระหนักว่าการไม่ทำอะไรเลย 1000000 ครั้งสามารถนำมาใช้โดยไม่มีรหัสไม่คุ้มค่ากับค่าเล็กน้อย แต่อย่างใดอย่างหนึ่งอาจไม่ต้องการขึ้นอยู่กับการเพิ่มประสิทธิภาพนี้เลย
Marc van Leeuwen

ยังเป็นไปได้ก็คือการให้การจัดสรรที่กำหนดเองที่หลีกเลี่ยงการก่อสร้างประเภทที่มีและการทำลายของวัตถุที่มีแต่อย่างเคร่งครัดนี้ละเมิดมาตรฐาน C ++ (ตั้งแต่ประเภทดังกล่าวจะไม่เริ่มต้น initialised) std::vectorstd::is_trivially_default_constructiblestd::is_trivially_destructible
วอลเตอร์

ยังstd::unique_ptrไม่ได้ให้การตรวจสอบที่ถูกผูกไว้ตรงกันข้ามกับการstd::vectorใช้งานจำนวนมาก
diapir

@diapir มันไม่เกี่ยวกับการดำเนินการ: ถูกต้องตามมาตรฐานการตรวจสอบขอบเขตในstd::vector .at()ฉันเดาว่าคุณหมายถึงว่าการใช้งานบางอย่างมีโหมดดีบักที่จะเช็คอิน.operator[]ด้วย แต่ฉันคิดว่ามันไร้ประโยชน์สำหรับการเขียนโค้ดแบบพกพาที่ดี
underscore_d

30

std::vectorสามารถคัดลอกไปรอบ ๆ ในขณะที่unique_ptr<int[]>ช่วยให้การแสดงความเป็นเจ้าของที่ไม่ซ้ำกันของอาร์เรย์ std::arrayในทางกลับกันขนาดต้องถูกกำหนดในเวลารวบรวมซึ่งอาจเป็นไปไม่ได้ในบางสถานการณ์


2
เพียงเพราะสิ่งที่สามารถคัดลอกไม่ได้หมายความว่ามันจะต้องมี
Nicol Bolas

4
@ NicolBolas: ฉันไม่เข้าใจ หนึ่งอาจต้องการที่จะป้องกันไม่ให้เหตุผลเดียวกันว่าทำไมใครจะใช้แทนunique_ptr shared_ptrฉันพลาดอะไรไปรึเปล่า?
Andy Prowl

4
unique_ptrทำมากกว่าป้องกันการใช้ผิดวัตถุประสงค์โดยไม่ตั้งใจ shared_ptrมันเป็นค่าใช้จ่ายนอกจากนี้ยังมีขนาดเล็กและต่ำกว่า ประเด็นก็คือในขณะที่มันเป็นเรื่องดีที่จะมีความหมายในชั้นเรียนที่ป้องกัน "การใช้ผิดวัตถุประสงค์" นั่นไม่ใช่เหตุผลเดียวที่จะใช้บางประเภท และvectorอยู่ไกลมีประโยชน์มากขึ้นในฐานะที่เป็นอาร์เรย์จัดเก็บกว่าunique_ptr<T[]>ถ้าด้วยเหตุผลอื่น ๆ นอกเหนือจากข้อเท็จจริงที่ว่ามันมีไม่มีขนาด
Nicol Bolas

3
ฉันคิดว่าฉันพูดให้ชัดเจน: มีเหตุผลอื่นที่จะใช้บางประเภทมากกว่านั้น เหมือนมีเหตุผลที่จะชอบvectorมากกว่าunique_ptr<T[]>ที่เป็นไปได้แทนที่จะพูดว่า "คุณไม่สามารถคัดลอกได้" และเลือกunique_ptr<T[]>เมื่อคุณไม่ต้องการคัดลอก การหยุดยั้งผู้อื่นจากการทำสิ่งที่ผิดไม่ใช่เหตุผลที่สำคัญที่สุดในการเลือกชั้นเรียน
Nicol Bolas

8
std::vectorมีค่าใช้จ่ายมากกว่า a std::unique_ptr- ใช้ ~ 3 pointers แทน ~ 1 std::unique_ptrบล็อกการคัดลอกการก่อสร้าง แต่เปิดใช้งานการเคลื่อนย้ายซึ่งถ้าหากความหมายของข้อมูลที่คุณกำลังทำงานอยู่สามารถย้ายได้ แต่ไม่ได้คัดลอกติดเชื้อที่classมีข้อมูลอยู่ การมีการดำเนินการกับข้อมูลที่ไม่ถูกต้องจริง ๆ แล้วทำให้คลาสคอนเทนเนอร์ของคุณแย่ลงและ "เพียงแค่ไม่ใช้" ไม่ได้กำจัดบาปทั้งหมด ต้องใส่ทุกตัวอย่างของคุณstd::vectorลงในคลาสที่คุณปิดการใช้งานด้วยตนเองmoveเป็นอาการปวดหัว มีstd::unique_ptr<std::array> size
Yakk - Adam Nevraumont

22

Scott Meyers มีสิ่งนี้เพื่อพูดใน Effective Modern C ++

การดำรงอยู่ของstd::unique_ptrสำหรับอาร์เรย์ควรจะเป็นเพียงความสนใจทางปัญญาให้กับคุณเพราะstd::array, std::vector, std::stringเป็นจริงดีกว่าเสมอตัวเลือกโครงสร้างข้อมูลอาร์เรย์กว่าดิบ เกี่ยวกับสถานการณ์เดียวที่ฉันสามารถนึกได้ว่าเมื่อstd::unique_ptr<T[]>ใดที่ต้องมีเหตุผลก็ต่อเมื่อคุณใช้ API แบบ C ซึ่งจะส่งกลับตัวชี้ดิบไปยังอาร์เรย์ฮีปที่คุณถือว่าเป็นเจ้าของ

ฉันคิดว่าคำตอบของ Charles Salvia นั้นมีความเกี่ยวข้องแม้ว่านั่นstd::unique_ptr<T[]>เป็นวิธีเดียวที่จะกำหนดค่าเริ่มต้นให้กับอาร์เรย์ที่ว่างเปล่าซึ่งไม่ทราบขนาดในเวลารวบรวม Scott Meyers จะพูดอะไรเกี่ยวกับแรงจูงใจในการใช้งานstd::unique_ptr<T[]>นี้


4
ดูเหมือนว่าเขาไม่ได้นึกถึงกรณีการใช้งานไม่กี่กรณีนั่นคือบัฟเฟอร์ที่มีขนาดคงที่ แต่ไม่ทราบเวลารวบรวมและ / หรือบัฟเฟอร์ที่เราไม่อนุญาตให้ทำสำเนา นอกจากนี้ยังมีประสิทธิภาพเป็นเหตุผลที่เป็นไปได้ที่จะชอบมันstackoverflow.com/a/24852984/2436175vector
อันโตนิโอ

17

ขัดกับstd::vectorและstd::array, std::unique_ptrสามารถเป็นเจ้าของตัวชี้โมฆะ
สิ่งนี้มีประโยชน์เมื่อทำงานกับ C APIs ที่คาดว่าจะเป็นอาร์เรย์หรือ NULL:

void legacy_func(const int *array_or_null);

void some_func() {    
    std::unique_ptr<int[]> ptr;
    if (some_condition) {
        ptr.reset(new int[10]);
    }

    legacy_func(ptr.get());
}

10

ฉันเคยใช้unique_ptr<char[]>พูลหน่วยความจำที่จัดสรรล่วงหน้าที่ใช้ในเอ็นจิ้นเกม แนวคิดคือจัดเตรียมพูลหน่วยความจำที่จัดสรรล่วงหน้าที่ใช้แทนการจัดสรรแบบไดนามิกสำหรับการส่งคืนผลลัพธ์การร้องขอการชนและสิ่งอื่น ๆ เช่นฟิสิกส์ของอนุภาคโดยไม่ต้องจัดสรร / หน่วยความจำว่างในแต่ละเฟรม มันค่อนข้างสะดวกสำหรับสถานการณ์ประเภทนี้ที่คุณต้องการพูลหน่วยความจำเพื่อจัดสรรวัตถุที่มีเวลา จำกัด (โดยทั่วไปคือหนึ่ง, 2 หรือ 3 เฟรม) ที่ไม่ต้องใช้ตรรกะในการทำลาย (เฉพาะการจัดสรรคืนหน่วยความจำ)


9

รูปแบบทั่วไปสามารถพบได้ในการเรียกWindows Win32 API บางอย่างซึ่งการใช้งานมีประโยชน์เช่นเมื่อคุณไม่ทราบว่าบัฟเฟอร์เอาต์พุตขนาดใหญ่เท่าไรเมื่อเรียก Win32 Win32 บางตัว (ที่จะเขียนข้อมูลบางอย่างภายใน บัฟเฟอร์นั้น):std::unique_ptr<T[]>

// Buffer dynamically allocated by the caller, and filled by some Win32 API function.
// (Allocation will be made inside the 'while' loop below.)
std::unique_ptr<BYTE[]> buffer;

// Buffer length, in bytes.
// Initialize with some initial length that you expect to succeed at the first API call.
UINT32 bufferLength = /* ... */;

LONG returnCode = ERROR_INSUFFICIENT_BUFFER;
while (returnCode == ERROR_INSUFFICIENT_BUFFER)
{
    // Allocate buffer of specified length
    buffer.reset( BYTE[bufferLength] );
    //        
    // Or, in C++14, could use make_unique() instead, e.g.
    //
    // buffer = std::make_unique<BYTE[]>(bufferLength);
    //

    //
    // Call some Win32 API.
    //
    // If the size of the buffer (stored in 'bufferLength') is not big enough,
    // the API will return ERROR_INSUFFICIENT_BUFFER, and the required size
    // in the [in, out] parameter 'bufferLength'.
    // In that case, there will be another try in the next loop iteration
    // (with the allocation of a bigger buffer).
    //
    // Else, we'll exit the while loop body, and there will be either a failure
    // different from ERROR_INSUFFICIENT_BUFFER, or the call will be successful
    // and the required information will be available in the buffer.
    //
    returnCode = ::SomeApiCall(inParam1, inParam2, inParam3, 
                               &bufferLength, // size of output buffer
                               buffer.get(),  // output buffer pointer
                               &outParam1, &outParam2);
}

if (Failed(returnCode))
{
    // Handle failure, or throw exception, etc.
    ...
}

// All right!
// Do some processing with the returned information...
...

คุณสามารถใช้std::vector<char>ในกรณีเหล่านี้
Arthur Tacca

@ArthurTacca - ... หากคุณไม่คำนึงว่าคอมไพเลอร์จะเริ่มต้นอักขระทุกตัวในบัฟเฟอร์ของคุณเป็น 0 หนึ่งต่อหนึ่ง
TED

9

ฉันต้องเผชิญกับกรณีที่ฉันต้องใช้std::unique_ptr<bool[]>ซึ่งอยู่ในห้องสมุด HDF5 (ห้องสมุดสำหรับการจัดเก็บข้อมูลไบนารีที่มีประสิทธิภาพใช้วิทยาศาสตร์เป็นจำนวนมาก) คอมไพเลอร์บางตัว (Visual Studio 2015 ในกรณีของฉัน) ให้การบีบอัดของstd::vector<bool> (โดยใช้ 8 bools ในทุกไบต์) ซึ่งเป็นหายนะสำหรับบางอย่างเช่น HDF5 ซึ่งไม่สนใจการบีบอัดนั้น ด้วยstd::vector<bool>HDF5 ในที่สุดก็อ่านขยะเพราะการบีบอัดนั้น

คิดว่าใครอยู่ที่นั่นเพื่อช่วยเหลือในกรณีที่std::vectorไม่ทำงานและฉันต้องจัดสรรอาเรย์แบบไดนามิกให้สะอาด :-)


9

สรุป: มันเป็นหน่วยความจำที่มีประสิทธิภาพมากที่สุด

A std::stringมาพร้อมกับตัวชี้ความยาวและบัฟเฟอร์ "short-string-optimization" แต่สถานการณ์ของฉันคือฉันต้องเก็บสตริงที่เกือบจะว่างเปล่าเสมอในโครงสร้างที่ฉันมีหลายแสน ใน C ฉันจะใช้char *และมันจะว่างเปล่าเกือบตลอดเวลา ซึ่งใช้งานได้กับ C ++ เช่นกันยกเว้นว่า a char *ไม่มี destructor และไม่ทราบว่าจะลบเอง ในทางตรงกันข้าม a std::unique_ptr<char[]>จะลบตัวเองเมื่ออยู่นอกขอบเขต ค่าว่างstd::stringใช้เวลาสูงสุด 32 ไบต์ แต่ค่าว่างนั้นstd::unique_ptr<char[]>ใช้ขนาด 8 ไบต์นั่นคือขนาดของตัวชี้

ข้อเสียที่ใหญ่ที่สุดคือทุกครั้งที่ฉันต้องการทราบความยาวของสตริงฉันต้องโทรหาstrlenมัน


3

เพื่อตอบคนที่คิดว่าคุณ "ต้อง" ใช้vectorแทนunique_ptrฉันมีกรณีในการเขียนโปรแกรม CUDA บน GPU เมื่อคุณจัดสรรหน่วยความจำในอุปกรณ์คุณต้องไปหาอาร์เรย์ตัวชี้ (พร้อมcudaMalloc) จากนั้นเมื่อดึงข้อมูลนี้ในโฮสต์คุณต้องกลับไปหาตัวชี้อีกครั้งและunique_ptrไม่สามารถจัดการตัวชี้ได้อย่างง่ายดาย ค่าใช้จ่ายเพิ่มเติมในการแปลงdouble*ไปvector<double>เป็นที่ไม่จำเป็นและนำไปสู่การสูญเสียของ perf


3

อีกเหตุผลหนึ่งที่อนุญาตและใช้งานstd::unique_ptr<T[]>ซึ่งยังไม่ได้กล่าวถึงในการตอบกลับ: ช่วยให้คุณสามารถประกาศประเภทองค์ประกอบของอาร์เรย์ได้

สิ่งนี้มีประโยชน์เมื่อคุณต้องการลดข้อความที่ถูกโยง#includeในส่วนหัวให้เล็กที่สุด (เพื่อเพิ่มประสิทธิภาพการสร้าง)

เช่น -

MyClass.h:

class ALargeAndComplicatedClassWithLotsOfDependencies;

class MyClass {
   ...
private:
   std::unique_ptr<ALargeAndComplicatedClassWithLotsOfDependencies[]> m_InternalArray;
};

MyClass.cpp:

#include "myclass.h"
#include "ALargeAndComplicatedClassWithLotsOfDependencies.h"

// MyClass implementation goes here

ที่มีโครงสร้างโค้ดข้างต้นทุกคนสามารถ#include "myclass.h"และการใช้งานโดยไม่ต้องรวมถึงการพึ่งพาการดำเนินงานภายในที่จำเป็นโดยMyClassMyClass::m_InternalArray

หากm_InternalArrayได้รับการประกาศเป็น a std::array<ALargeAndComplicatedClassWithLotsOfDependencies>หรือ a std::vector<...>ตามลำดับผลลัพธ์จะพยายามใช้ชนิดที่ไม่สมบูรณ์ซึ่งเป็นข้อผิดพลาดในการคอมไพล์


สำหรับกรณีการใช้งานนี้โดยเฉพาะฉันเลือกใช้รูปแบบ Pimpl เพื่อแยกการพึ่งพา - ถ้าใช้เฉพาะในแบบส่วนตัวคำจำกัดความจะถูกเลื่อนออกไปจนกว่าจะใช้วิธีการเรียน class ALargeAndComplicatedClassWithLotsOfDependenciesถ้ามันใช้ต่อสาธารณชนแล้วผู้ใช้ของชั้นเรียนควรมีอยู่แล้วได้ความรู้เกี่ยวกับคอนกรีต ดังนั้นในทางตรรกะคุณไม่ควรเจอสถานการณ์แบบนั้น

3

ฉันไม่เห็นด้วยกับวิญญาณของคำตอบที่ยอมรับอย่างมากพอ "เครื่องมือสุดท้าย" ไกลจากมัน!

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

ดังนั้นเมื่อต้องการอาเรย์คำตอบของคำถามต่อไปนี้จะระบุพฤติกรรมของมัน: 1. ขนาดของมันคือ a) ไดนามิกที่รันไทม์หรือ b) คงที่ แต่รู้จักกันเฉพาะที่รันไทม์หรือ c) คงที่และเป็นที่รู้จักในเวลารวบรวม? 2. สามารถจัดสรรอาเรย์บนสแต็กได้หรือไม่?

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

       Dynamic     |   Runtime static   |         Static
Stack std::vector      unique_ptr<T[]>          std::array
Heap  std::vector      unique_ptr<T[]>     unique_ptr<std::array>

ใช่ฉันคิดว่าunique_ptr<std::array>ควรได้รับการพิจารณาและไม่เป็นเครื่องมือสุดท้าย แค่คิดว่าสิ่งที่เหมาะที่สุดกับอัลกอริทึมของคุณ

ทั้งหมดเหล่านี้เข้ากันได้กับ API C ธรรมดาผ่านตัวชี้ดิบไปยังอาร์เรย์ข้อมูล ( vector.data()/ array.data()/ uniquePtr.get())

ป.ล. นอกเหนือจากข้อควรพิจารณาข้างต้นแล้วยังมีความเป็นเจ้าของอีกอย่างหนึ่ง: std::arrayและstd::vectorมีความหมายตามตัวอักษร (มีการสนับสนุนดั้งเดิมสำหรับการคัดลอกและส่งผ่านค่า) ในขณะที่unique_ptr<T[]>สามารถย้ายได้เท่านั้น (บังคับให้เจ้าของคนเดียว) อาจมีประโยชน์ในสถานการณ์ต่าง ๆ ในทางตรงกันข้ามอาร์เรย์คงที่ธรรมดา ( int[N]) และอาร์เรย์ไดนามิกธรรมดา ( new int[10]) เสนอไม่ควรหลีกเลี่ยงหากเป็นไปได้ - ซึ่งควรเป็นไปได้ในกรณีส่วนใหญ่ ถ้านั่นยังไม่เพียงพออาเรย์แบบไดนามิกธรรมดาก็ไม่สามารถสืบค้นขนาดของมันได้ - โอกาสพิเศษสำหรับความเสียหายของหน่วยความจำและช่องโหว่ด้านความปลอดภัย


2

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

unique_ptr<byte[]> data = get_some_data();

threadpool->post_work([](void* param) { do_a_thing(unique_ptr<byte[]>((byte*)param)); },
                      data.release());

เราทุกคนต้องการสิ่งที่ดีสำหรับเรา C ++ สำหรับเวลาอื่น ๆ


2

unique_ptr<char[]>สามารถใช้ที่คุณต้องการประสิทธิภาพของ C และความสะดวกสบายของ C ++ พิจารณาว่าคุณต้องทำงานกับคนนับล้าน (ตกลงพันล้านถ้าคุณยังไม่ไว้ใจ) ของสตริง จัดเก็บแต่ละรายการแยกกันstringหรือvector<char>วัตถุจะเป็นหายนะสำหรับรูทีนการจัดการหน่วยความจำ (ฮีป) โดยเฉพาะถ้าคุณต้องการจัดสรรและลบสตริงที่แตกต่างกันหลายครั้ง

อย่างไรก็ตามคุณสามารถจัดสรรบัฟเฟอร์เดียวสำหรับการจัดเก็บสตริงจำนวนมาก คุณไม่ต้องการchar* buffer = (char*)malloc(total_size);เหตุผลที่ชัดเจน (หากไม่ชัดเจนให้ค้นหา "ทำไมใช้ smart ptrs") คุณต้องการที่จะunique_ptr<char[]> buffer(new char[total_size]);

โดยการเปรียบเทียบการพิจารณาประสิทธิภาพและความสะดวกสบายแบบเดียวกันนั้นนำไปใช้กับcharข้อมูลที่ไม่ใช่(พิจารณาจำนวนเวกเตอร์ / เมทริกซ์ / วัตถุ)


หนึ่งไม่ได้ใส่พวกเขาทั้งหมดในที่ใหญ่vector<char>? คำตอบที่ผมคิดว่าเป็นเพราะพวกเขาจะเป็นศูนย์-initialised unique_ptr<char[]>เมื่อคุณสร้างบัฟเฟอร์ในขณะที่พวกเขาจะไม่ได้ถ้าคุณใช้ แต่นักเก็ตหลักคนนี้หายไปจากคำตอบของคุณ
Arthur Tacca

2
  • คุณต้องการโครงสร้างของคุณเพื่อให้มีเพียงตัวชี้สำหรับเหตุผลความเข้ากันได้ของไบนารี
  • คุณต้องเชื่อมต่อกับ API ที่คืนค่าหน่วยความจำด้วย new[]
  • บริษัท หรือโครงการของคุณมีกฎทั่วไปเกี่ยวกับการใช้std::vectorตัวอย่างเช่นเพื่อป้องกันโปรแกรมเมอร์ที่ไม่ประมาทจากการแนะนำสำเนาโดยไม่ตั้งใจ
  • คุณต้องการป้องกันโปรแกรมเมอร์ที่ประมาทไม่ให้แนะนำสำเนาในกรณีนี้โดยไม่ได้ตั้งใจ

มีกฎทั่วไปที่จะต้องมีการแนะนำให้ใช้คอนเทนเนอร์ C ++ แทนการพอยน์เตอร์ของคุณเอง เป็นกฎทั่วไป มันมีข้อยกเว้น ยังมีอีก; นี่เป็นเพียงตัวอย่าง


0

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

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