make_unique и идеальная пересылка - PullRequest
213 голосов
/ 12 августа 2011

Почему в стандартной библиотеке C ++ 11 нет шаблона функции std::make_unique?Я нахожу

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)...?Что компилятор делает из этого?

Ответы [ 6 ]

155 голосов
/ 11 марта 2012

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

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

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

Редактировать: std::make_unique теперь является частью C ++ 14 .

76 голосов
/ 22 ноября 2012

Хорошо, но Стефан Т. Лававей (более известный как 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.

19 голосов
/ 12 августа 2011

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, это было бы немного удобнее. Ничего больше.

19 голосов
/ 12 августа 2011

Хотя ничто не мешает вам написать свой собственный помощник, я считаю, что основная причина предоставления make_shared<T> в библиотеке заключается в том, что она фактически создает внутренний тип общего указателя, отличный от shared_ptr<T>(new T), который назначается по-другому, иНет способа достичь этого без специального помощника.

С другой стороны, ваша оболочка make_unique - это просто синтаксический сахар вокруг выражения new, поэтому, хотя это может показаться приятным для глаз,он ничего не приносит new в таблицу. Исправление: на самом деле это не так: наличие вызова функции для переноса выражения new обеспечивает безопасность исключений, например, вслучай, когда вы вызываете функцию void f(std::unique_ptr<A> &&, std::unique_ptr<B> &&).Наличие двух необработанных new, которые не упорядочены по отношению друг к другу, означает, что если одно новое выражение завершится неудачно с исключением, другое может привести к утечке ресурсов.Что касается того, почему в стандарте нет make_unique: это было просто забыто.(Это иногда случается. В стандарте также нет глобального std::cbegin, хотя он и должен быть.)

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

13 голосов
/ 12 августа 2011

В 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)

Последнее, по-моему, неверно.

Кроме того, пакет аргументов нельзя передавать в функцию без расширения.Я не уверен насчет пакета параметров шаблона.

5 голосов
/ 03 января 2013

Вдохновленный реализацией Стефана Т. Лававея, я подумал, что было бы неплохо иметь 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); 
...