Make_unique и совершенная переадресация

Почему в стандартной библиотеке С++ 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)...? Что делает компилятор из этого?

Ответ 1

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

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

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

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

Ответ 2

Хороший, но Stephan T. Lavavej (более известный как 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 С++ 6.

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

Ответ 3

В то время как ничто не мешает вам писать свой собственный помощник, я считаю, что основной причиной предоставления 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, который использует стирание типа для хранения пользовательских удалений, не делая их частью типа.

Ответ 4

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

Ответ 5

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

Последнее неверно, я думаю.

Кроме того, пакет аргументов не может быть передан функции, нерасширенной. Я не уверен в пакете параметров шаблона.

Ответ 6

Вдохновленный реализацией 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);