ความแตกต่างระหว่าง std :: make_unique และ std :: unique_ptr กับ new


130

ไม่std::make_uniqueได้มีผลประโยชน์ใด ๆ เช่นประสิทธิภาพstd::make_shared?

เมื่อเทียบกับการสร้างด้วยตนเองstd::unique_ptr:

std::make_unique<int>(1);         // vs
std::unique_ptr<int>(new int(1));

ไม่make_sharedได้มีประสิทธิภาพมากกว่าเพียงแค่เขียนโค้ดมือยาว?
Ed Heal

9
@EdHeal อาจเป็นเพราะmake_sharedสามารถจัดสรรทั้งพื้นที่สำหรับวัตถุและพื้นที่สำหรับบล็อกควบคุมร่วมกันในการจัดสรรเดียว ต้นทุนของสิ่งนั้นคือไม่สามารถยกเลิกการจัดสรรออบเจ็กต์แยกต่างหากจากบล็อกควบคุมได้ดังนั้นหากคุณใช้weak_ptrมากคุณอาจต้องใช้หน่วยความจำเพิ่มขึ้น
bames53

บางทีนี่อาจเป็นจุดเริ่มต้นที่ดีstackoverflow.com/questions/9302296/…
Ed Heal

คำตอบ:


140

แรงจูงใจที่อยู่เบื้องหลังmake_uniqueส่วนใหญ่เป็นสองเท่า:

  • make_uniqueปลอดภัยสำหรับการสร้างชั่วคราวในขณะที่การใช้งานอย่างชัดเจนnewคุณต้องจำกฎเกี่ยวกับการไม่ใช้ไลบรารีที่ไม่มีชื่อ

    foo(make_unique<T>(), make_unique<U>()); // exception safe
    
    foo(unique_ptr<T>(new T()), unique_ptr<U>(new U())); // unsafe*
    
  • นอกจากนี้make_uniqueในที่สุดก็หมายความว่าเราสามารถบอกคนที่จะใช้ 'ไม่เคย' newมากกว่ากฎก่อนที่จะ " 'ไม่เคย' ใช้newยกเว้นเมื่อคุณทำunique_ptr"

นอกจากนี้ยังมีเหตุผลที่สาม:

  • make_uniqueไม่ต้องการการใช้งานประเภทซ้ำซ้อน unique_ptr<T>(new T())->make_unique<T>()

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

* คาดว่า C ++ 17 จะรวมการเปลี่ยนแปลงกฎซึ่งหมายความว่าสิ่งนี้ไม่ปลอดภัยอีกต่อไป ดู c ++ เอกสารคณะกรรมการP0400R0และP0145R3


มันจะสมเหตุสมผลกว่าที่จะพูดstd::unique_ptrและstd::shared_ptrเป็นเหตุผลที่เราสามารถบอกให้ผู้คน "ไม่ใช้new"
Timothy Shields

2
@TimothyShields ใช่นั่นคือสิ่งที่ฉันหมายถึง มันเป็นเพียงแค่ใน C ++ 11 ที่เรามีmake_sharedและmake_uniqueชิ้นสุดท้ายที่ขาดหายไปก่อนหน้านี้
bames53

1
วิธีใดที่คุณสามารถพูดถึงสั้น ๆ หรือเชื่อมโยงไปยังสาเหตุของการไม่ใช้ Temporaries ที่ไม่มีชื่อ?
Dan Nissenbaum

14
ที่จริงจากstackoverflow.com/a/19472607/368896ผมได้รับมัน ... จากคำตอบที่พิจารณาการเรียกฟังก์ชั่นดังต่อไปนี้f: f(unique_ptr<T>(new T), function_that_can_throw());- ที่จะพูดคำตอบ: คอมไพเลอร์ได้รับอนุญาตให้โทร (ตามลำดับ): new T, function_that_can_throw(), unique_ptr<T>(...). เห็นได้ชัดว่าถ้าfunction_that_can_throwขว้างจริงแล้วคุณรั่ว make_uniqueป้องกันกรณีนี้ ดังนั้นคำถามของฉันมีคำตอบ
Dan Nissenbaum

3
เหตุผลหนึ่งที่ครั้งหนึ่งฉันต้องใช้ std :: unique_ptr <T> (new T ()) เนื่องจากตัวสร้างของ T เป็นแบบส่วนตัว แม้ว่าการเรียกไปที่ std :: make_unique จะอยู่ในเมธอด public factory ของคลาส T แต่ก็ไม่ได้คอมไพล์เนื่องจากหนึ่งในเมธอดพื้นฐานของ std :: make_unique ไม่สามารถเข้าถึงคอนสตรัคเตอร์ส่วนตัวได้ ฉันไม่ต้องการให้เมธอดนั้นเป็นเพื่อนเพราะฉันไม่ต้องการพึ่งพาการใช้งาน std :: make_unique ดังนั้นวิธีแก้ปัญหาเดียวคือเรียกใหม่ในเมธอดคลาส T จากโรงงานของฉันแล้วรวมไว้ใน std :: unique_ptr <T>
Patrick

14

std::make_uniqueและstd::make_sharedมีสองเหตุผล:

  1. เพื่อที่คุณจะได้ไม่ต้องแสดงรายการอาร์กิวเมนต์ประเภทเทมเพลตอย่างชัดเจน
  2. ข้อยกเว้นเพิ่มเติมความปลอดภัยในการใช้งานstd::unique_ptrหรือตัวstd::shared_ptrสร้าง (ดูส่วนหมายเหตุที่นี่ )

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


นอกจากนี้ยังมีเพื่อความปลอดภัยเป็นพิเศษ
0x499602D2

@ 0x499602D2 และนั่นก็ดีนอกจากนี้ เพจนี้พูดถึงเรื่องนั้น
Timothy Shields

สำหรับผู้อ่านในอนาคต C ++ 17 ไม่อนุญาตให้มีการแทรกระหว่างอาร์กิวเมนต์ของฟังก์ชันดังนั้นอาร์กิวเมนต์เพื่อความปลอดภัยจะไม่ถูกระงับอีกต่อไป การจัดสรรหน่วยความจำสำหรับสองขนานstd::make_sharedจะช่วยให้มั่นใจได้ว่าอย่างน้อยหนึ่งในนั้นถูกรวมไว้ในตัวชี้อัจฉริยะก่อนที่การจัดสรรหน่วยความจำอื่นจะเกิดขึ้นจึงไม่มีการรั่วไหล
MathBunny

7

เหตุผลที่คุณต้องใช้std::unique_ptr(new A())หรือstd::shared_ptr(new A())โดยตรงแทนstd::make_*()คือไม่สามารถเข้าถึงตัวสร้างของคลาสที่Aอยู่นอกขอบเขตปัจจุบัน


0

พิจารณาการเรียกใช้ฟังก์ชัน

void function(std::unique_ptr<A>(new A()), std::unique_ptr<B>(new B())) { ... }

สมมติว่าใหม่A()ประสบความสำเร็จ แต่ใหม่B()โยนข้อยกเว้น: คุณจับมันเพื่อกลับมาทำงานตามปกติของโปรแกรมของคุณ น่าเสียดายที่มาตรฐาน C ++ ไม่ต้องการให้อ็อบเจ็กต์ A ถูกทำลายและหน่วยความจำถูกยกเลิกการจัดสรร: หน่วยความจำรั่วไหลอย่างเงียบ ๆ และไม่มีวิธีใดที่จะทำความสะอาดได้ โดยการห่อ A และ B เข้าไปstd::make_uniquesคุณมั่นใจว่าจะไม่มีการรั่วไหล:

void function(std::make_unique<A>(), std::make_unique<B>()) { ... }

ประเด็นตรงนี้คือstd::make_unique<A>และstd::make_unique<B>ตอนนี้เป็นอ็อบเจ็กต์ชั่วคราวและการล้างอ็อบเจ็กต์ชั่วคราวถูกระบุอย่างถูกต้องในมาตรฐาน C ++: ตัวทำลายของพวกมันจะถูกทริกเกอร์และหน่วยความจำจะถูกปลดปล่อย ดังนั้นถ้าคุณสามารถมักจะชอบที่จะจัดสรรวัตถุที่ใช้และstd::make_uniquestd::make_shared

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