Почему в С++ 14 нет функции std:: allocate_unique?

Почему shared_ptr имеет allocate_shared, а unique_ptr не имеет allocate_unique?
Я хотел бы сделать unique_ptr с помощью своего собственного распределителя: нужно ли мне самому распределить буфер и назначить его уникальному_ptr?
Это кажется очевидной идиомой.

Ответ 1

Почему shared_ptr имеет allocate_shared, а unique_ptr не имеет allocate_unique?

shared_ptr нуждается в нем, чтобы он мог выделять свое внутреннее общее состояние (счетчик ссылок и удаление), а также общий объект, используя распределитель. unique_ptr управляет только объектом; поэтому нет необходимости предоставлять распределитель самому unique_ptr и меньше необходимости в функции allocate.

(По той же причине также меньше потребности в make_unique, вероятно, поэтому она не присутствовала в С++ 11, но была добавлена ​​в С++ 14 по большому спросу ради согласованности. Возможно, тот же спрос добавит allocate_unique к будущему стандарту.)

Я должен сам выделить буфер и назначить его уникальному_ptr?

Да. Или вы можете написать свой собственный allocate_unique; в отличие от allocate_shared, возможно и достаточно просто реализовать его отдельно от unique_ptr. (Как уже упоминалось в комментариях, вам нужно убедиться, что он использовал соответствующий деаэратор для распределителя, дефолт по умолчанию использовал бы delete и пошел бы ужасно неправильно).

Это кажется очевидной идиомой.

Действительно. Но так много других идиом, и не все может (или должно) быть стандартизованным.

Для более официального обоснования нынешнего отсутствия allocate_unique, см. предложение для make_unique, в частности раздел 4 (Custom Deleters).

Ответ 2

Я должен сам выделить буфер, а затем назначить его уникальному_ptr?

Не только буфер, указатель на объект. Но объект может быть уничтожен распределителем, и память определенно должна быть освобождена распределителем, поэтому вам также нужно передать распределителю unique_ptr. Он не знает, как использовать распределитель, поэтому вам нужно обернуть его в пользовательский удаленный элемент и станет частью типа unique_ptr.

Я думаю, что общее решение будет выглядеть примерно так:

#include <memory>

template<typename Alloc>
struct alloc_deleter
{
  alloc_deleter(const Alloc& a) : a(a) { }

  typedef typename std::allocator_traits<Alloc>::pointer pointer;

  void operator()(pointer p) const
  {
    Alloc aa(a);
    std::allocator_traits<Alloc>::destroy(aa, std::addressof(*p));
    std::allocator_traits<Alloc>::deallocate(aa, p, 1);
  }

private:
  Alloc a;
};

template<typename T, typename Alloc, typename... Args>
auto
allocate_unique(const Alloc& alloc, Args&&... args)
{
  using AT = std::allocator_traits<Alloc>;
  static_assert(std::is_same<typename AT::value_type, std::remove_cv_t<T>>{}(),
                "Allocator has the wrong value_type");

  Alloc a(alloc);
  auto p = AT::allocate(a, 1);
  try {
    AT::construct(a, std::addressof(*p), std::forward<Args>(args)...);
    using D = alloc_deleter<Alloc>;
    return std::unique_ptr<T, D>(p, D(a));
  }
  catch (...)
  {
    AT::deallocate(a, p, 1);
    throw;
  }
}

int main()
{
  std::allocator<int> a;
  auto p = allocate_unique<int>(a, 0);
  return *p;
}