make_unique та ідеальне переадресація


216

Чому std::make_uniqueв стандартній бібліотеці C ++ 11 немає шаблону функцій? я знайшов

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

трохи багатослівний. Не було б наступне набагато приємніше?

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.
Керрек СБ

5
@Kerrek: Я не думаю, що було б доцільно параметризувати make_uniqueкористувацький делетер, тому що, очевидно, він виділяє через звичайний старий newі, отже, повинен використовувати звичайний старий delete:)
fredoverflow

1
@Fred: Це правда. Отже, пропоноване make_uniqueобмежуватиметься newвиділенням ... ну, це добре, якщо ви хочете його написати, але я можу зрозуміти, чому щось подібне не входить у стандарт.
Керрек СБ

4
Насправді мені подобається використовувати make_uniqueшаблон, оскільки конструктор std::unique_ptrявний, і тому повертається unique_ptrз функції багатослівно . Крім того , я б вважав за краще використовувати auto p = make_unique<foo>(bar, baz)ніж std::unique_ptr<foo> p(new foo(bar, baz)).
Олександр К.

5
make_uniqueзаходить C++14, дивіться isocpp.org/blog/2013/04/trip-report-iso-c-spring-2013-meeting
stefan

Відповіді:


157

Герб Саттер, голова комітету зі стандартизації C ++, пише у своєму блозі :

Те, що C ++ 11 не включає make_unique, частково є недоглядом, і це майже напевно додасться в майбутньому.

Він також дає реалізацію, ідентичну тій, яку дає ОП.

Редагувати: std::make_unique зараз є частиною C ++ 14 .


2
make_uniqueШаблон функції не сама по собі гарантує виключення в безпеки викликів. Він покладається на умовність, що користувач його використовує. На відміну від цього, сувора статична перевірка типу (яка є основною різницею між C ++ і C) будується на ідеї забезпечення безпеки за допомогою типів. А для цього make_uniqueможе бути просто клас замість функції. Наприклад, дивіться мою статтю в блозі від травня 2010 року. Це також пов'язане з обговоренням у блозі Herb.
Ура та хт. - Альф

1
@ DavidRodríguez-dribeas: прочитайте блог Herb, щоб зрозуміти проблему безпеки виключень. забудьте про "не призначений для отримання", це просто сміття. замість моєї пропозиції make_uniqueскласти клас, тепер я думаю, що краще зробити його функцією, яка виробляє a make_unique_t, причиною цього є проблема з самим збоченим розбором :-).
Ура та хт. - Альф

1
@ Cheersandhth.-Альф: Можливо, ми прочитали іншу статтю, оскільки в тій, що я щойно прочитав, чітко зазначено, що 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)))...
Девід Родрігес - дрибес

2
@ Cheersandhth.-Альф: Я запитав про проблеми виключення в make_unique реалізованих як вище, і ви вказуєте мені на статтю Саттера, статтю, в якій мій google-fu вказав на мене, що make_uniqueпропонує гарантію винятку , яка суперечить вашій заяві вище. Якщо у вас є інша стаття I буду зацікавлений в читанні. Отже, моє оригінальне запитання стоїть: Як make_unique(як визначено вище) не є виключенням безпечним? (Sidenote: так, я думаю, що це make_unique покращує безпеку виключень у місцях, де його можна застосувати)
David Rodríguez - dribeas

3
... також я не переслідую менш безпечну у використанні make_uniqueфункцію. По-перше, я не бачу, наскільки це небезпечно, і не бачу, як додавання додаткового типу зробить його більш безпечним . Що я знаю, це те, що мені цікаво зрозуміти проблеми, які може мати ця реалізація - я не бачу ніяких-- і те, як альтернативна реалізація вирішила б їх. Які умовності, щоmake_unique залежить? Як би ви використовували перевірку типу для забезпечення безпеки? Це два питання, на які я хотів би відповісти.
Девід Родрігес - дрибес

78

Приємно, але Стефан Т. Лававей (більш відомий як 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 .

Оновлена ​​версія версії make_unique STL тепер доступна як N3656 . Ця версія була прийнята в проекті C ++ 14.


make_unique був запропонований Стівеном Т Лавалеєм у наступне оновлення std.
томник

Ось як він говорив про його додавання. channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-/…
tominator

чому вносити всі непотрібні редагування xeo, було добре, як було. Цей код був таким самим, як його висловив Стівен Т. Лавалей, і він працює над dinkumware, який підтримує бібліотеку std. Ви вже прокоментували цю стіну, тож слід знати.
томінатор

3
Реалізація для make_uniqueмає йти в заголовку. Заголовки не повинні імпортувати простір імен (див. Пункт №59 у книзі Саттера / Александреску "Стандарти кодування C ++"). Зміни Xeo допомагають уникнути заохочення до поганих практик.
Брет Кунс

на жаль, не підтримується VC2010, навіть VC2012 я думаю, що обидва не підтримують параметр
vaidic tempalte

19

std::make_sharedце не просто скорочення std::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
Це не тільки для зручності, але також підвищить безпеку винятків у певних випадках. Подивіться на це відповідь Керрека СБ.
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> &&). Наявність двох неочищених news, які не мають наслідків один щодо одного, означає, що якщо один новий вираз не вдається за винятком, інший може витікати ресурси. Щодо того, чого немає make_uniqueв стандарті: це було просто забуто. (Це трапляється епізодично. Також std::cbeginу стандарті немає глобального, навіть якщо його має бути.)

Також зауважте, що unique_ptrбереться другий параметр шаблону, який слід якось дозволити; це відрізняється від того shared_ptr, який використовує стирання типу для зберігання користувальницьких делетерів, не роблячи їх частиною типу.


1
@FredOverflow: загальний покажчик - відносно складний клас; всередині він зберігає поліморфний блок управління, але існує кілька різних видів керуючих блоків. shared_ptr<T>(new T)використовує один із них, make_shared<T>()використовує інший. Допускаючи, що це хороша річ, і версія для спільного використання - це в деякому сенсі найлегший загальний покажчик, який ви можете отримати.
Керрек СБ

15
@FredOverflow: shared_ptrвиділяє блок динамічної пам’яті, щоб продовжувати підрахунок та дію «disposer» під час створення shared_ptr. Якщо ви чітко передаєте вказівник, йому потрібно створити "новий" блок, якщо ви використовуєте make_sharedйого, можна об'єднати ваш об'єкт і супутникові дані в один блок пам'яті (один new), що призводить до швидшого розподілу / розстановки, менше фрагментації та (як правило, ) краща поведінка кешу.
Маттьє М.

2
Я думаю, що мені потрібно переглянути перегляд shared_ptr та друзів ...
fredoverflow

4
-1 "З іншого боку, ваша обгортка make_unique - це просто синтаксичний цукор навколо нового виразу, тому, хоча це може здатися приємним для очей, воно не приносить нічого нового в стіл." неправильно. Це приносить можливість виклику безпечної виклику функції. Однак це не приносить гарантії; для цього потрібно мати клас, щоб формальні аргументи могли бути оголошені для цього класу (Герб описав це як різницю між відключенням та відмовою).
Ура та хт. - Альф

1
@ Cheersandhth.-Alf: Так, це правда. З тих пір я це зрозумів. Я відредагую відповідь.
Керрек СБ

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
Приємно, щоб це було прописано. Чому б також не включити параметр варіативного шаблону?
Керрек СБ

@Kerrek: тому що я не дуже впевнений у його дивному синтаксисі, і не знаю, чи багато людей грали. Тому я буду тримати те, що знаю. Це може бути гарантією запису на відповіді c ++, якщо хтось був досить вмотивований і знаючий, тому що варіативний синтаксис є досить повним.
Матьє М.

Синтаксис є std::forward<Args>(args)..., який розширюється на forward<T1>(x1), forward<T2>(x2), ....
Керрек СБ

1
: Я думаю, що forwardнасправді завжди потрібен параметр шаблону, чи не так?
Керрек СБ

1
@Howard: правильно! Примусовий примірник спрацьовує застереження ( ideone.com/GDNHb )
М.

5

Надихнувшись реалізацією Стефана Т. Лававей, я подумав, що може бути приємно мати make_unique, який підтримує розширення масиву, він знаходиться на github, і я хотів би отримати коментарі до нього. Це дозволяє зробити це:

// 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); 
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.