Навіщо використовувати std :: make_unique в C ++ 17?


96

Наскільки я розумію, C ++ 14 запроваджено std::make_unique, оскільки в результаті невказаного порядку оцінки параметрів це було небезпечно:

f(std::unique_ptr<MyClass>(new MyClass(param)), g()); // Syntax A

(Пояснення: якщо оцінка спочатку виділяє пам’ять для необробленого вказівника, тоді виклики g()та виняток видаються перед std::unique_ptrпобудовою, тоді пам’ять витікає.)

Виклик std::make_uniqueбув способом обмежити порядок дзвінків, роблячи таким чином речі безпечними:

f(std::make_unique<MyClass>(param), g());             // Syntax B

З тих пір C ++ 17 уточнив порядок оцінки, зробивши Синтаксис A також безпечним, тому ось моє запитання: чи є ще причина використовувати конструктор std::make_uniqueover std::unique_ptrу C ++ 17? Ви можете навести кілька прикладів?

Наразі єдиною причиною, яку я можу уявити, є те, що вона дозволяє друкувати MyClassлише один раз (припускаючи, що вам не потрібно покладатися на поліморфізм за допомогою std::unique_ptr<Base>(new Derived(param))). Однак це здається досить слабкою причиною, особливо коли std::make_uniqueне дозволяє вказати видалювач, тоді як std::unique_ptrконструктор робить це.

І, щоб було зрозуміло, я не виступаю за вилучення std::make_uniqueзі Стандартної бібліотеки (зберігаючи це має сенс принаймні для зворотної сумісності), а навпаки, цікавлячись, чи існують ситуації, в яких настійно кращеstd::unique_ptr


4
Однак це здається досить слабкою причиною -> Чому це слабкою причиною? Це ефективно зменшує дублювання коду типу. Що стосується видаляча, як часто ви використовуєте спеціальний видалювач, коли використовуєте std::unique_ptr? Це не аргумент протиmake_unique
llllllllll

2
Я кажу, що це слабка причина, тому що якби її не було std::make_uniqueспочатку, я не думаю, що це було б достатньою причиною, щоб додати її до STL, особливо коли це синтаксис, менш виражений, ніж використання конструктора, не більше
Вічний

1
Якщо у вас є програма, створена в c ++ 14, за допомогою make_unique, ви не хочете, щоб функція була видалена зі stl. Або якщо ви хочете, щоб він був сумісний із зворотною стороною.
Серж

2
@Serge Це хороший момент, але це трохи крім мети мого запитання. Я
Вічний

1
@Eternal, будь ласка, припиніть посилатися на стандартну бібліотеку C ++ як STL, оскільки вона є неправильною і створює плутанину. Див stackoverflow.com/questions/5205491 / ...
Marandil

Відповіді:


74

Ви маєте рацію, що основну причину було видалено. Все ще існують нові вказівки щодо використання, і це менше причин набору тексту (не потрібно повторювати тип або використовувати слово new). Слід визнати, що це не вагомі аргументи, але мені дуже подобається не бачити newу своєму коді.

Також не забувайте про консистенцію. Ви абсолютно повинні використовувати, make_sharedтому використання make_uniqueє природним і відповідає шаблону. Це той тривіальним зміна std::make_unique<MyClass>(param)до std::make_shared<MyClass>(param)(або навпаки) , де синтаксис вимагає набагато більше перезапису.


41
@reggaeguitar Якщо я бачу, newмені потрібно зупинитися і подумати: як довго буде жити цей вказівник? Я правильно впорався? Якщо є виняток, чи все прибрано правильно? Я хотів би не задавати собі ці питання і не витрачати на це свій час, а якщо я цим не користуюся new, я не повинен задавати ці питання.
NathanOliver

5
Уявіть, що ви робите grep по всіх вихідних файлах вашого проекту і не знайшли жодного new. Хіба це не було б чудово?
Себастьян Мах

5
Головною перевагою рекомендацій "не використовувати нове" є те, що це просто, тому це просте керівництво для менш досвідчених розробників, з якими ви можете працювати. Спочатку я не розумів, але це має значення саме по собі
Вічний

@NathanOliver Ви насправді не обов'язково повинні використовувати std::make_shared- уявіть випадок, коли виділений об'єкт великий і на нього std::weak_ptrвказує багато -s: було б краще дозволити об'єкту видалятись, як тільки останній спільний покажчик знищений, і він живе лише на невеликій спільній території.
Dev Null,

1
@NathanOliver ви б цього не зробили. Я говорю про недолік std::make_shared stackoverflow.com/a/20895705/8414561, коли пам’ять, яка використовувалася для зберігання об’єкта, не може бути звільнена до останнього std::weak_ptr(навіть якщо всі std::shared_ptr-s, що вказують на нього (і отже, сам об'єкт) вже знищений).
Dev Null,

50

make_uniqueвідрізняє Tвід T[]і T[N], unique_ptr(new ...)ні.

Ви можете легко отримати невизначену поведінку, передавши вказівник, який був new[]відредагований до a unique_ptr<T>, або передавши вказівник, який був newвідредагований до a unique_ptr<T[]>.


Це гірше: це не тільки не робить, це просто неможливо.
Deduplicator

22

Причиною є коротший код без дублікатів. Порівняйте

f(std::unique_ptr<MyClass>(new MyClass(param)), g());
f(std::make_unique<MyClass>(param), g());

Ви економите MyClass, newі фігурні дужки. Це варто тільки один символ більше косметики в порівнянні з PTR .


2
Ну, як я вже сказав у запитанні, я бачу, що це менше набору тексту лише з одним згадуванням MyClass, але мені було цікаво, чи є вагоміша причина для його використання
Вічний

2
У багатьох випадках посібник з відрахування допоможе усунути <MyClass>деталь у першому варіанті.
AnT

9
Це вже було сказано в коментарях щодо інших відповідей, але хоча c ++ 17 ввів відрахування типу шаблону для конструкторів, у випадку std::unique_ptrце заборонено. Це пов’язано з розрізненням std::unique_ptr<T>іstd::unique_ptr<T[]>
Вічний

19

Кожне використання newмає бути ретельно перевірено на предмет правильності використання протягом усього життя; це видаляється? Тільки один раз?

Кожне використання make_uniqueне для цих додаткових характеристик; поки об'єкт-власник має "правильний" час життя, він рекурсивно робить унікальний покажчик "правильним".

Тепер, це правда , що unique_ptr<Foo>(new Foo())ідентично у всіх відносинах 1 до make_unique<Foo>(); для цього просто потрібен простіший "grep ваш вихідний код для будь-якого використання newдля їх аудиту".


1 насправді брехня в загальному випадку. Ідеальне пересилання не є ідеальним, {}за замовчуванням init, масиви - це виключення.


Технічно unique_ptr<Foo>(new Foo)не зовсім ідентично make_unique<Foo>()... останньому, new Foo()але в іншому випадку так.
Barry

@barry true, можливий перевантажений оператор new.
Якк - Адам Неврамонт,

@dedup, що це за фол відьмак C ++ 17?
Якк - Адам Неврамонт,

2
@Deduplicator, в той час як c ++ 17 ввів відрахування типу шаблону для конструкторів, якщо std::unique_ptrце заборонено. Якщо це пов’язано з розрізненням std::unique_ptr<T>іstd::unique_ptr<T[]>
Вічний

@ Yakk-AdamNevraumont Я не мав на увазі перевантаження нового, я просто мав на увазі default-init проти value-init.
Barry

0

З тих пір C ++ 17 уточнив порядок оцінки, зробивши Синтаксис A також безпечним

Це насправді недостатньо добре. Покладання на нещодавно введене технічне застереження як гарантію безпеки - не надто надійна практика:

  • Хтось може скомпілювати цей код у C ++ 14.
  • Ви заохочуєте використовувати raw newв іншому місці, наприклад, шляхом копіювання вашого прикладу.
  • Як припускає SM, оскільки існує дублювання коду, один тип може бути змінений без зміни іншого.
  • Якийсь автоматичний рефакторинг IDE може перемістити це в newінше місце (гаразд, надано, мало шансів на це).

Як правило, гарно, щоб ваш код був відповідним / надійним / чітко дійсним, не вдаючись до розкладання мови, шукаючи незначні або неясні технічні положення стандарту.

(це, по суті, той самий аргумент, який я тут наводив щодо порядку знищення кортежу.)


-1

Розглянемо функцію void (std :: unique_ptr (new A ()), std :: unique_ptr (new B ())) {...}

Припустимо, що новий A () вдається, але новий B () видає виняток: ви ловите його, щоб відновити нормальне виконання вашої програми. На жаль, стандарт C ++ не вимагає знищення об’єкта A і вивільнення його пам’яті: пам’ять безшумно витікає, і немає можливості її очистити. Обертаючи A і B в std :: make_uniques, ви впевнені, що витоку не відбудеться:

void function (std :: make_unique (), std :: make_unique ()) {...} Справа в тому, що std :: make_unique і std :: make_unique тепер є тимчасовими об'єктами, і очищення тимчасових об'єктів правильно вказано в стандарт C ++: їх деструктори будуть спрацьовувати і пам’ять звільнятиметься. Тож, якщо можете, завжди віддайте перевагу виділенню об’єктів за допомогою std :: make_unique та std :: make_shared.


4
Автор чітко зазначив, що в C ++ 17 витоки більше не відбуватимуться. "З тих пір C ++ 17 уточнив порядок оцінки, зробивши Синтаксис також безпечним, тому ось моє запитання: (...)". Ви не відповіли на його запитання.
R2RT
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.