make_unique และการส่งต่อที่สมบูรณ์แบบ


216

เหตุใดจึงไม่มีstd::make_uniqueเทมเพลตฟังก์ชันในไลบรารี C ++ 11 มาตรฐาน ฉันหา

std::unique_ptr<SomeUserDefinedType> p(new SomeUserDefinedType(1, 2, 3));

verbose เล็กน้อย ต่อไปนี้จะดีกว่านี้ไหม

auto p = std::make_unique<SomeUserDefinedType>(1, 2, 3);

สิ่งนี้ซ่อนสิ่งที่newดีและกล่าวถึงเพียงครั้งเดียวเท่านั้น

อย่างไรก็ตามนี่คือความพยายามของฉันในการดำเนินการmake_unique:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

ฉันใช้เวลาพอสมควรในการstd::forwardรวบรวมสิ่งต่าง ๆ แต่ฉันไม่แน่ใจว่ามันถูกต้องหรือไม่ ใช่ไหม? std::forward<Args>(args)...หมายความว่าอะไรกันแน่? คอมไพเลอร์ทำอะไรได้บ้าง


1
ค่อนข้างมั่นใจว่าเราได้มีการสนทนานี้ก่อน ... นอกจากนี้ยังทราบว่าunique_ptrใช้พารามิเตอร์แม่แบบที่สองที่คุณอย่างใดควรอนุญาตให้มี - shared_ptrที่แตกต่างจาก
Kerrek SB

5
@Kerrek: ผมไม่คิดว่ามันจะทำให้รู้สึกถึง parameterize make_uniqueกับ Deleter กำหนดเองเพราะเห็นได้ชัดว่ามันจัดสรรผ่านเก่าธรรมดาnewและด้วยเหตุนี้ต้องใช้ธรรมดาเก่าdelete:)
fredoverflow

1
@ เฟรด: จริง ดังนั้นข้อเสนอmake_uniqueจะ จำกัด อยู่ที่การnewจัดสรร ... ก็ดีถ้าคุณต้องการเขียน แต่ฉันสามารถเห็นได้ว่าทำไมบางอย่างเช่นนี้จึงไม่ใช่ส่วนหนึ่งของมาตรฐาน
Kerrek SB

4
ที่จริงแล้วฉันชอบที่จะใช้make_uniqueแม่แบบเนื่องจากตัวสร้างของstd::unique_ptrชัดเจนและดังนั้นจึงเป็นเรื่องที่จะกลับมาunique_ptrจากฟังก์ชั่น นอกจากนี้ผมอยากจะใช้กว่าauto p = make_unique<foo>(bar, baz) std::unique_ptr<foo> p(new foo(bar, baz))
Alexandre C.

5
make_uniqueกำลังเข้ามาC++14ดูisocpp.org/blog/2013/04/trip-report-iso-c-spring-2013-meeting
stefan

คำตอบ:


157

Herb Sutter เก้าอี้ของคณะกรรมการมาตรฐาน C ++ เขียนไว้ในบล็อกของเขา:

C ++ 11 ที่ไม่รวมmake_uniqueนั้นเป็นส่วนหนึ่งของการกำกับดูแลและจะเพิ่มเข้ามาในอนาคต

นอกจากนี้เขายังให้การใช้งานที่เหมือนกันกับที่ได้รับจาก OP

แก้ไข: std::make_uniqueตอนนี้เป็นส่วนหนึ่งของภาษา C ++ 14


2
make_uniqueแม่แบบฟังก์ชั่นของตัวเองไม่ได้รับประกันความปลอดภัยสายข้อยกเว้น มันขึ้นอยู่กับการประชุมว่าผู้โทรใช้มัน ในทางตรงกันข้ามการตรวจสอบประเภทสแตติกที่เข้มงวด (ซึ่งเป็นความแตกต่างหลักระหว่าง C ++ และ C) สร้างขึ้นจากแนวคิดของการบังคับใช้ความปลอดภัยผ่านประเภทต่างๆ และสำหรับสิ่งนั้นmake_uniqueสามารถเป็นคลาสแทนฟังก์ชันได้ ตัวอย่างเช่นดูบทความบล็อกของฉันตั้งแต่เดือนพฤษภาคม 2010 นอกจากนี้ยังเชื่อมโยงกับจากการอภิปรายในบล็อกของ Herb
ไชโยและ hth - Alf

1
@ DavidRodríguez-dribeas: อ่านบล็อกของ Herb เพื่อทำความเข้าใจกับข้อยกเว้นด้านความปลอดภัย ลืมเกี่ยวกับ "ไม่ได้รับการออกแบบมาให้ได้" นั่นเป็นเพียงขยะ แทนที่จะเป็นข้อเสนอแนะของฉันในการทำmake_uniqueชั้นเรียนฉันคิดว่ามันจะดีกว่าที่จะทำให้มันเป็นฟังก์ชั่นที่ก่อให้เกิดmake_unique_tเหตุผลที่เป็นปัญหากับการแยกวิเคราะห์วิปริตที่สุด :-)
ไชโยและ hth - Alf

1
@ Cheersandhth. -Alf: บางทีเราได้อ่านบทความอื่นแล้วเพราะสิ่งที่ฉันเพิ่งอ่านชัดเจนระบุว่าmake_uniqueรับประกันข้อยกเว้นที่แข็งแกร่ง หรือบางทีคุณกำลังผสมเครื่องมือกับการใช้งานซึ่งในกรณีนี้ไม่มีฟังก์ชั่นที่ปลอดภัยยกเว้น พิจารณาvoid f( int *, int* ){}ให้การno throwรับประกันอย่างชัดเจนแต่โดยเหตุผลของคุณมันไม่ใช่ข้อยกเว้นที่ปลอดภัยเนื่องจากอาจถูกนำไปใช้ในทางที่ผิด ที่แย่กว่านั้นvoid f( int, int ) {}ก็ไม่ใช่ข้อยกเว้นที่ปลอดภัยเช่นกัน: typedef unique_ptr<int> up; f( *up(new int(5)), *up(new int(10)))...
David Rodríguez - dribeas

2
@ Cheersandhth. -Alf: ฉันถามเกี่ยวกับปัญหาข้อยกเว้นใน make_uniqueการนำไปใช้ข้างต้นและคุณนำฉันไปที่บทความโดย Sutter บทความที่ google-fu ของฉันชี้ให้ฉันทราบถึงรัฐที่make_uniqueมีการรับประกันข้อยกเว้นที่แข็งแกร่งซึ่งขัดแย้งกับคำสั่งของคุณด้านบน หากคุณมีบทความที่แตกต่างกันผมรู้สึกสนใจในการอ่านมัน ดังนั้นคำถามเดิมของฉันหมายถึงว่าmake_unique(ยกเว้นตามที่ระบุไว้ข้างต้น) ไม่ปลอดภัยอย่างไร (Sidenote: ใช่ฉันคิดว่าmake_unique ปรับปรุงความปลอดภัยยกเว้นในสถานที่ที่สามารถนำไปใช้)
David Rodríguez - dribeas

3
... และฉันก็ไม่ได้ใช้make_uniqueฟังก์ชั่นที่ปลอดภัยน้อยกว่า ครั้งแรกที่ฉันไม่เห็นว่าคนนี้เป็นที่ไม่ปลอดภัยและผมไม่เห็นว่าการเพิ่มชนิดพิเศษจะทำให้มันปลอดภัยมากขึ้น สิ่งที่ฉันรู้คือฉันสนใจที่จะทำความเข้าใจกับปัญหาที่การดำเนินการนี้มี - ฉันไม่สามารถมองเห็นได้ - และวิธีการดำเนินการทางเลือกอื่น ๆ จะแก้ไขได้อย่างไร อะไรคือการประชุมที่make_uniqueขึ้นอยู่กับ? คุณจะใช้การตรวจสอบประเภทเพื่อบังคับใช้ความปลอดภัยอย่างไร นี่คือคำถามสองข้อที่ฉันจะรักคำตอบ
David Rodríguez - dribeas

78

นิสัยดี แต่ Stephan T. Lavavej (รู้จักกันในชื่อ STL) มีวิธีแก้ปัญหาที่ดีกว่าสำหรับmake_uniqueซึ่งทำงานได้อย่างถูกต้องสำหรับรุ่นอาร์เรย์

#include <memory>
#include <type_traits>
#include <utility>

template <typename T, typename... Args>
std::unique_ptr<T> make_unique_helper(std::false_type, Args&&... args) {
  return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

template <typename T, typename... Args>
std::unique_ptr<T> make_unique_helper(std::true_type, Args&&... args) {
   static_assert(std::extent<T>::value == 0,
       "make_unique<T[N]>() is forbidden, please use make_unique<T[]>().");

   typedef typename std::remove_extent<T>::type U;
   return std::unique_ptr<T>(new U[sizeof...(Args)]{std::forward<Args>(args)...});
}

template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
   return make_unique_helper<T>(std::is_array<T>(), std::forward<Args>(args)...);
}

สามารถดูได้ในวิดีโอ Core C ++ 6 ของเขา

รุ่นปรับปรุงของรุ่น STL ของ make_unique ขณะนี้ใช้ได้เป็นN3656 รุ่นนี้มีการนำไปใช้กับแบบร่าง C ++ 14


make_unique ได้รับการเสนอโดย Stephen T Lavalej ในการอัพเดทครั้งต่อไป
เสนอรางวัล

นี่คือสิ่งที่เขาต้องการพูดถึงเกี่ยวกับการเพิ่ม channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-/…
ครอบครอง

ทำไมต้องทำการแก้ไข xeo ที่ไม่จำเป็นทั้งหมดมันก็ดีเหมือนเดิม รหัสนั้นเหมือนกับ Stephen T. Lavalej วางไว้และเขาทำงานกับ dinkumware ซึ่งดูแลรักษาไลบรารี std คุณได้แสดงความคิดเห็นบนผนังนั้นแล้วดังนั้นคุณควรรู้
เสนอราคา

3
การดำเนินการสำหรับmake_uniqueควรไปในส่วนหัว ส่วนหัวไม่ควรนำเข้าเนมสเปซ (ดูรายการ # 59 ในหนังสือ "C ++ มาตรฐานการเข้ารหัส" ของ Sutter / Alexandrescu) การเปลี่ยนแปลงของ Xeo ช่วยหลีกเลี่ยงการส่งเสริมการปฏิบัติที่ไม่ดี
Bret Kuhns

น่าเสียดายที่ VC2010 ไม่รองรับแม้กระทั่ง VC2012 ฉันคิดว่าทั้งคู่ไม่รองรับพารามิเตอร์
varpalic tempalte

19

std::make_sharedstd::shared_ptr<Type> ptr(new Type(...));ไม่ได้เป็นเพียงชวเลข มันทำสิ่งที่คุณไม่สามารถทำถ้าขาดมัน

เพื่อที่จะทำงานได้std::shared_ptrต้องจัดสรรบล็อกการติดตามเพิ่มเติมเพื่อเก็บที่เก็บสำหรับตัวชี้จริง อย่างไรก็ตามเนื่องจากstd::make_sharedจัดสรรวัตถุจริงจึงเป็นไปได้ที่จะstd::make_sharedจัดสรรทั้งวัตถุและบล็อกติดตามในบล็อกหน่วยความจำเดียวกัน

ดังนั้นในขณะที่std::shared_ptr<Type> ptr = new Type(...);จะจัดสรรสองหน่วยความจำ (หนึ่งสำหรับnew, หนึ่งในstd::shared_ptrบล็อกการติดตาม) std::make_shared<Type>(...)จะจัดสรรหนึ่งหน่วยความจำบล็อก

std::shared_ptrนั่นเป็นสิ่งสำคัญสำหรับผู้ที่มีศักยภาพหลายแห่ง สิ่งเดียวที่std::make_uniqueจะทำคือสะดวกขึ้นเล็กน้อย ไม่มีอะไรมากไปกว่านั้น


2
มันไม่จำเป็น คำแนะนำ แต่ไม่จำเป็น
ลูกสุนัข

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

19

ในขณะที่ไม่มีอะไรหยุดคุณจากการเขียนผู้ช่วยของคุณเองฉันเชื่อว่าเหตุผลหลักmake_shared<T>ในการให้บริการในห้องสมุดก็คือว่ามันสร้างตัวชี้ที่ใช้ร่วมกันภายในชนิดที่แตกต่างกว่าshared_ptr<T>(new T)ที่จัดสรรไว้แตกต่างกันและไม่มีวิธีใดที่จะบรรลุเป้าหมายนี้ ผู้ช่วย

make_uniqueเสื้อคลุมของคุณในอีกทางหนึ่งเป็นเพียงประโยคน้ำตาลรอบ ๆnewสำนวนดังนั้นในขณะที่มันอาจดูเป็นที่ชื่นชอบ แต่มันไม่ได้นำอะไรnewมาวางบนโต๊ะ การแก้ไข:นี้ไม่ได้ในความเป็นจริงจริง: มีการเรียกใช้ฟังก์ชันการตัดการแสดงออกให้ความปลอดภัยยกเว้นเช่นในกรณีที่คุณเรียกฟังก์ชั่นnew void f(std::unique_ptr<A> &&, std::unique_ptr<B> &&)การมี raw สองตัวnewที่ไม่ได้เกิดตามมาด้วยความเคารพซึ่งกันและกันหมายความว่าหากการแสดงออกใหม่ล้มเหลวโดยมีข้อยกเว้น สำหรับสาเหตุที่ไม่มีmake_uniqueมาตรฐาน: มันถูกลืมไปแล้ว (สิ่งนี้เกิดขึ้นเป็นครั้งคราวและยังไม่มีstd::cbeginมาตรฐานระดับโลกแม้ว่าจะควรมีอยู่ก็ตาม)

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


1
@FredOverflow: ตัวชี้ที่ใช้ร่วมกันเป็นคลาสที่ค่อนข้างซับซ้อน ภายในจะเก็บบล็อกควบคุมอ้างอิง polymorphic แต่มีบล็อกควบคุมหลายประเภท shared_ptr<T>(new T)ใช้หนึ่งในนั้นmake_shared<T>()ใช้อีกอันหนึ่ง การอนุญาตให้ใช้เป็นสิ่งที่ดีและรุ่นที่ใช้การแบ่งปันนั้นบางตัวชี้ที่ใช้ร่วมกันที่มีน้ำหนักเบาที่สุดที่คุณจะได้รับ
Kerrek SB

15
@FredOverflow: shared_ptrจัดสรรบล็อกของหน่วยความจำแบบไดนามิกเพื่อให้ทันการนับและ "disposer" shared_ptrการกระทำเมื่อคุณสร้าง หากคุณผ่านตัวชี้อย่างชัดเจนก็ต้องสร้างบล็อก "ใหม่" ถ้าคุณใช้make_sharedจะสามารถกำของวัตถุและข้อมูลดาวเทียมในบล็อกเดียวของหน่วยความจำ (หนึ่งnew) ส่งได้เร็วขึ้น / จัดสรร deallocation กระจายตัวน้อยลงและ (ปกติ ) พฤติกรรมแคชที่ดีขึ้น
Matthieu M.

2
ฉันคิดว่าฉันต้องอีกครั้งนาฬิกาshared_ptr และเพื่อน ๆ ...
fredoverflow

4
-1 "เสื้อคลุม make_unique ของคุณในทางกลับกันเป็นเพียงน้ำตาล syntactic รอบการแสดงออกใหม่ดังนั้นในขณะที่มันอาจจะดูพอใจตา แต่มันก็ไม่ได้นำสิ่งใหม่มาสู่โต๊ะ" มันผิด. มันทำให้เกิดความเป็นไปได้ของการเรียกใช้ฟังก์ชันที่ปลอดภัยยกเว้น อย่างไรก็ตามไม่ได้เป็นการรับประกัน สำหรับสิ่งนั้นจะต้องเป็นคลาสเพื่อให้สามารถมีการโต้แย้งอย่างเป็นทางการของคลาสนั้น (Herb อธิบายว่าเป็นความแตกต่างระหว่างการเลือกเข้าร่วมและการไม่เข้าร่วม)
ไชโยและ hth - Alf

1
@ Cheersandhth. -Alf: ใช่นั่นเป็นเรื่องจริง ฉันรู้ตั้งแต่นี้แล้ว ฉันจะแก้ไขคำตอบ
Kerrek SB

13

ใน C ++ 11 ...ใช้ (ในรหัสเทมเพลต) สำหรับ "แพ็คขยาย" เช่นกัน

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

ตัวอย่างเช่นการสร้างตัวอย่างของคุณ:

std::forward<Args>(args)... -> std::forward<int>(1), std::forward<int>(2),
                                                     std::forward<int>(3)

std::forward<Args...>(args...) -> std::forward<int, int, int>(1,2,3)

ฉันคิดว่าไม่ถูกต้อง

นอกจากนี้ชุดของอาร์กิวเมนต์อาจไม่ถูกส่งไปยังฟังก์ชันที่ไม่ขยาย ฉันไม่แน่ใจเกี่ยวกับชุดพารามิเตอร์เทมเพลต


1
ยินดีที่ได้เห็นการสะกดคำนี้ ทำไมไม่รวมพารามิเตอร์เทมเพลต variadic ด้วย
Kerrek SB

@ Kerrek: เพราะฉันไม่แน่ใจเกี่ยวกับไวยากรณ์ที่แปลกประหลาดของมันและฉันไม่รู้ว่าถ้ามีหลายคนเล่นด้วย ดังนั้นฉันจะเก็บสิ่งที่ฉันรู้ มันอาจรับประกันรายการ c ++ คำถามที่พบบ่อยถ้าใครบางคนได้รับแรงบันดาลใจและมีความรู้เพียงพอสำหรับไวยากรณ์ variadic นั้นค่อนข้างสมบูรณ์
Matthieu M.

ไวยากรณ์ซึ่งจะขยายstd::forward<Args>(args)... forward<T1>(x1), forward<T2>(x2), ...
Kerrek SB

1
: ฉันคิดว่าforwardจริง ๆ แล้วต้องการพารามิเตอร์เทมเพลตใช่ไหม
Kerrek SB

1
@ วิธี: ใช่! การบังคับให้สร้างอินสแตนซ์ทริกเกอร์คำเตือน ( ideone.com/GDNHb )
Matthieu M.

5

แรงบันดาลใจจากการใช้งานโดย Stephan T. Lavavej ฉันคิดว่ามันอาจจะดีถ้ามี make_unique ที่รองรับส่วนขยายของอาร์เรย์มันใช้ gitub และฉันชอบที่จะรับความคิดเห็น อนุญาตให้คุณทำสิ่งนี้:

// create unique_ptr to an array of 100 integers
auto a = make_unique<int[100]>();

// create a unique_ptr to an array of 100 integers and
// set the first three elements to 1,2,3
auto b = make_unique<int[100]>(1,2,3); 
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.