Зачем использовать 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, особенно когда это синтаксис , который менее выразительна , чем с помощью конструктора, не более
Eternal
1
Если у вас есть программа, созданная на C ++ 14 с использованием make_unique, вы не хотите, чтобы функция удалялась из stl. Или, если вы хотите, чтобы он был обратно совместим.
Serge
2
@Serge Это хороший аргумент, но он немного выходит за рамки моего вопроса. Внесу правку, чтобы было понятнее
Eternal
1
@Eternal, пожалуйста, перестаньте ссылаться на стандартную библиотеку C ++ как на STL, поскольку это неверно и создает путаницу. См stackoverflow.com/questions/5205491/...
Marandil

Ответы:

76

Вы правы, что главную причину убрали. По-прежнему существуют правила не использовать новые правила и меньше причин для набора текста (не нужно повторять тип или использовать слово 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
Основное преимущество правила «не использовать новое» состоит в том, что он прост, поэтому его легко можно дать менее опытным разработчикам, с которыми вы, возможно, работаете. Сначала я не понял, но это имеет ценность само по себе
Eternal
@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
52

make_uniqueотличает Tот T[]и T[N], unique_ptr(new ...)не делает.

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

Калет
источник
Еще хуже: это не только не так, но и просто невозможно.
Дедупликатор
22

Причина в том, чтобы код был короче, без дубликатов. Сравнить

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

Вы экономите MyClass, newи фигурные скобки. Стоимость make всего на один символ больше, чем у ptr .

SM
источник
2
Что ж, как я сказал в вопросе, я вижу, что он меньше печатает только с одним упоминанием MyClass, но мне было интересно, есть ли более веская причина для его использования
Eternal
2
Во многих случаях руководство по дедукции помогает исключить <MyClass>деталь в первом варианте.
AnT
9
Об этом уже говорилось в комментариях к другим ответам, но хотя в С ++ 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 your source code for all use of newto audit it ».


1 на самом деле ложь в общем случае. Идеальная пересылка не идеальна, {}инициализация по умолчанию, все массивы - исключения.

Якк - Адам Неврамонт
источник
Технически unique_ptr<Foo>(new Foo)не совсем идентично make_unique<Foo>()последнему. new Foo()Но в остальном - да.
Барри
@barry true, возможен перегруженный оператор new.
Якк - Адам Неврамонт
@dedup, что это за мерзкое колдовство C ++ 17?
Якк - Адам Неврамонт
2
@Deduplicator, в то время как в c ++ 17 введено выведение типа шаблона для конструкторов, если std::unique_ptrоно запрещено. Если имеет отношение к различению std::unique_ptr<T>иstd::unique_ptr<T[]>
Eternal
@ Yakk-AdamNevraumont Я не имел в виду перегрузку нового, я просто имел в виду default-init и value-init.
Барри
0

С тех пор C ++ 17 уточнил порядок оценки, сделав синтаксис A безопасным.

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

  • Кто-то может скомпилировать этот код на C ++ 14.
  • Вы могли бы поощрять использование raw в newдругом месте, например, путем копирования вашего примера.
  • Как предполагает SM, поскольку существует дублирование кода, один тип может быть изменен без изменения другого.
  • Какой-то автоматический рефакторинг IDE может переместить это в newдругое место (хорошо, конечно, шансов на это мало).

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

(по сути, это тот же аргумент, который я привел здесь относительно порядка уничтожения кортежей.)

Эйнпоклум
источник
-1

Рассмотрим функцию void (std :: unique_ptr (new A ()), std :: unique_ptr (new B ())) {...}

Предположим, что new A () завершился успешно, но new 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 уточнил порядок оценки, сделав синтаксис A безопасным, поэтому вот мой вопрос: (...)». Вы не ответили на его вопрос.
R2RT