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.
Kerrek SB

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

1
@Fred: это правда. Таким образом, предложенное 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)).
Александр К.

Ответы:


157

Херб Саттер, председатель комитета по стандартизации C ++, пишет в своем блоге :

То, что C ++ 11 не включает make_unique, отчасти является упущением и почти наверняка будет добавлено в будущем.

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

Изменить: std::make_unique теперь является частью C ++ 14 .


2
make_uniqueШаблон функции не сама по себе гарантирует исключения в безопасности вызовов. Это основано на соглашении, что вызывающая сторона использует это. Напротив, строгая статическая проверка типов (которая является основной разницей между C ++ и C) основана на идее обеспечения безопасности посредством типов. И для этого, make_uniqueможет быть просто класс вместо функции. Например, смотрите мою статью в блоге от мая 2010 года. Это также связано с обсуждением в блоге Херба.
ура и hth. Альф

1
@ DavidRodríguez-dribeas: прочитайте блог Херба, чтобы понять проблему безопасности исключений. забудьте о "не предназначенном для получения", это просто мусор. вместо того, чтобы предлагать создать make_uniqueкласс, теперь я думаю, что лучше сделать его функцией, которая производит a make_unique_t, причина этого - проблема с самым извращенным синтаксическим анализом :-).
ура и hth. - Альф

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)))...
Дэвид Родригес - dribeas

2
@ Cheersandhth.-Alf: Я спросил о вопросах исключений в make_unique реализована , как описано выше , и вы мне точку на статью Sutter, статьи , что мой Google-фу указал мне на государства , которые make_uniqueпредлагают сильную гарантию исключения , что противоречит вашему заявлению выше. Если у вас есть другая статья , я буду заинтересован в чтении. Итак, мой первоначальный вопрос стоит. Как make_unique(как определено выше) не исключение безопасно? (Примечание: да, я думаю, что это make_unique повышает безопасность исключений в местах, где это может быть применено)
Дэвид Родригес - dribeas

3
... также я не преследую менее безопасную make_uniqueфункцию. Во-первых, я не понимаю, как это небезопасно, и не вижу, как добавление дополнительного типа сделает его более безопасным . Что я знаю, так это то, что мне интересно понять проблемы, которые могут возникнуть у этой реализации - я не вижу никаких - и как альтернативная реализация могла бы их решить. Какие конвенции , что make_uniqueзависит? Как бы вы использовали проверку типов для обеспечения безопасности? Это два вопроса, на которые я хотел бы получить ответ.
Дэвид Родригес - dribeas

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 .

Обновленная версия STL make_unique теперь доступна как N3656 . Эта версия была принята в проект C ++ 14.


make_unique был предложен Стивеном Т Лавалей в следующем обновлении std.
Tominator

Вот как бы он говорил о добавлении. channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-/...
tominator

зачем делать все ненужные правки xeo, все было нормально как было. Этот код был точно таким, как сказал Стивен Т. Лавалей, и он работает на dinkumware, который поддерживает библиотеку std. Вы уже прокомментировали эту стену, так что вы должны знать.
Tominator

3
Реализация для make_uniqueдолжна идти в заголовке. Заголовки не должны импортировать пространство имен (см. Пункт № 59 в книге Саттера / Александреску «Стандарты кодирования C ++»). Изменения Xeo помогают избежать поощрения плохой практики.
Брет Кунс

к сожалению, не поддерживается VC2010, даже VC2012, я думаю, который не поддерживает параметр varidic tempalte
zhaorufei

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
Это не только для удобства, это также улучшит безопасность исключений в некоторых случаях. См. Ответ 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> &&). Наличие двух необработанных news, которые не упорядочены по отношению друг к другу, означает, что если одно новое выражение завершится неудачно с исключением, другое может привести к утечке ресурсов. Что касается того, почему нет make_uniqueв стандарте: это было просто забыто. (Это иногда случается. Там также нет глобальныхstd::cbegin значений, хотя они и должны быть.)

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


1
@FredOverflow: разделяемый указатель является относительно сложным классом; внутренне он сохраняет полиморфный контрольный блок управления, но есть несколько различных типов контрольных блоков. shared_ptr<T>(new T)использует один из них, make_shared<T>()использует другой. Допуская, что это хорошо, и версия make-shared в некотором смысле является самым легким разделяемым указателем, который вы можете получить.
Kerrek SB

15
@FredOverflow: shared_ptrвыделяет блок динамической памяти, чтобы вести счет и действие «disposer» при создании shared_ptr. Если вы передаете указатель явно, ему нужно создать «новый» блок, если вы make_sharedего используете, он может связать ваш объект и спутниковые данные в один блок памяти (один new), что приведет к более быстрому распределению / освобождению, меньшей фрагментации и (обычно) ) лучшее поведение кеша.
Матье М.

2
Я думаю, что мне нужно пересмотреть shared_ptr и друзей ...
fredoverflow

4
-1 «С другой стороны, ваша оболочка make_unique - это просто синтаксический сахар вокруг нового выражения, поэтому, хотя это может показаться приятным для глаз, оно не приносит ничего нового в таблицу». неправильно. Это дает возможность безопасного вызова функции исключения. Однако это не дает гарантии; для этого он должен быть классом, чтобы можно было объявить формальные аргументы этого класса (Херб описал это как разницу между opt-in и opt-out).
ура и hth. - Альф

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
Приятно видеть это прописано. Почему бы не включить параметр шаблона variadic?
Kerrek SB

@Kerrek: потому что я не уверен в его странном синтаксисе, и я не знаю, много ли людей играли с ним. Так что я буду придерживаться того, что знаю. Это может потребовать записи в FAQ c ++, если кто-то был мотивирован и достаточно осведомлен, так как синтаксис вариаций достаточно полон.
Матье М.

Синтаксис std::forward<Args>(args)..., который расширяется до forward<T1>(x1), forward<T2>(x2), ....
Kerrek SB

1
Я думаю, что на forwardсамом деле всегда требуется параметр шаблона, не так ли?
Kerrek SB

1
@ Ховард: правильно! Принудительное создание экземпляра вызывает предупреждение ( ideone.com/GDNHb )
Матье М.

5

Вдохновленный реализацией Stephan T. Lavavej, я подумал, что было бы неплохо иметь 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.