Почему в C ++ 17 нет std :: construct_at? - PullRequest
0 голосов
/ 24 октября 2018

C ++ 17 добавляет std::destroy_at, но не существует std::construct_at аналога.Это почему?Разве это не может быть реализовано так просто, как показано ниже?

template <typename T, typename... Args>
T* construct_at(void* addr, Args&&... args) {
  return new (addr) T(std::forward<Args>(args)...);
}

Что позволило бы избежать этого не совсем естественного размещения нового синтаксиса:

auto ptr = construct_at<int>(buf, 1);  // instead of 'auto ptr = new (buf) int(1);'
std::cout << *ptr;
std::destroy_at(ptr);

Ответы [ 6 ]

0 голосов
/ 21 марта 2019

std::construct_at был добавлен в C ++ 20.Бумага, которая сделала это, - Больше контейнеров constexpr .Предполагается, что этого не было достаточно для того, чтобы размещать новое в C ++ 17, но C ++ 20 меняет положение вещей.

Цель предложения, добавившего эту функцию, заключается в поддержке выделения памяти constexpr, включаяstd::vector.Для этого требуется способность создавать объекты в выделенном хранилище.Тем не менее, просто размещение новых сделок в терминах void *, а не T *.constexpr В настоящее время у оценки нет возможности получить доступ к исходному хранилищу, и комитет хочет сохранить его таким образом.Библиотечная функция std::construct_at добавляет типизированный интерфейс constexpr T * construct_at(T *, Args && ...).

Это также имеет то преимущество, что пользователь не должен указывать тип создаваемого типа;это выводится из типа указателя.Синтаксис для правильного вызова размещения нового типа ужасен и нелогичен.Сравните std::construct_at(ptr, args...) с ::new(static_cast<void *>(ptr)) std::decay_t<decltype(*ptr)>(args...).

0 голосов
/ 01 ноября 2018

Я думаю, что должна быть стандартная функция конструкции.На самом деле libc ++ содержит одну деталь реализации в файле stl_construct.h.

namespace std{
...
  template<typename _T1, typename... _Args>
    inline void
    _Construct(_T1* __p, _Args&&... __args)
    { ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }
...
}

Я думаю, что это полезно, потому что это позволяет сделать "размещение нового" другом.Это отличная точка настройки для типа «только для перемещения», которому требуется uninitialized_copy в кучу по умолчанию (например, из элемента std::initializer_list).


У меня есть собственная библиотека контейнеров, которая переопределяетdetail::uninitialized_copy (из диапазона) для использования пользовательского detail::construct:

namespace detail{
    template<typename T, typename... As>
    inline void construct(T* p, As&&... as){
        ::new(static_cast<void*>(p)) T(std::forward<As>(as)...);
    }
}

, который объявлен другом класса только для перемещения, чтобы разрешить копирование только в контексте размещения new.

template<class T>
class my_move_only_class{
    my_move_only_class(my_move_only_class const&) = default;
    friend template<class TT, class...As> friend void detail::construct(TT*, As&&...);
public:
    my_move_only_class(my_move_only_class&&) = default;
    ...
};
0 голосов
/ 24 октября 2018

std::destroy_at обеспечивает два объективных улучшения по сравнению с прямым вызовом деструктора:

  1. Это уменьшает избыточность:

    T *ptr = new T;
    //Insert 1000 lines of code here.
    ptr->~T(); //What type was that again?
    

    Конечно, мы все предпочли бы простооберните его в unique_ptr и покончите с этим, но если по какой-то причине этого не произойдет, в T есть элемент избыточности.Если мы изменим тип на U, теперь мы должны изменить вызов деструктора или все прервется.Использование std::destroy_at(ptr) устраняет необходимость менять одну и ту же вещь в двух местах.

    DRY - это хорошо.

  2. Это облегчает задачу:

    auto ptr = allocates_an_object(...);
    //Insert code here
    ptr->~???; //What type is that again?
    

    Если мы вывели тип указателя, то удалить его становится довольно сложно.Вы не можете сделать ptr->~decltype(ptr)();так как синтаксический анализатор C ++ не работает таким образом.Мало того, что decltype выводит тип как указатель , поэтому вам нужно будет удалить косвенность указателя из выведенного типа.Приводит вас:

    auto ptr = allocates_an_object(...);
    //Insert code here
    using delete_type = std::remove_pointer_t<decltype(ptr)>;
    ptr->~delete_type();
    

    А кто хочет набрать , что ?

В отличие от этого, ваш гипотетический std::construct_at не дает цель улучшения по сравнению с размещением new.Вы должны указать тип, который вы создаете в обоих случаях.Параметры для конструктора должны быть предоставлены в обоих случаях.Указатель на память должен быть предоставлен в обоих случаях.

Таким образом, нет необходимости решать ваши гипотетические std::construct_at.

И он объективно менее способен чем размещение нового.Вы можете сделать это:

auto ptr1 = new(mem1) T;
auto ptr2 = new(mem2) T{};

Это разные .В первом случае объект инициализируется по умолчанию, что может сделать его неинициализированным.Во втором случае объект инициализируется значением.

Ваш гипотетический std::construct_at не может позволить вам выбрать тот, который вы хотите.Он может иметь код, который выполняет инициализацию по умолчанию, если вы не укажете параметры, но тогда он не сможет предоставить версию для инициализации значения.И это может иметь значение initialize без параметров, но тогда вы не можете по умолчанию инициализировать объект.

0 голосов
/ 24 октября 2018

construct, по-видимому, не дает никакого синтаксического сахара.Кроме того, это менее эффективно, чем размещение новых.Привязка к ссылочным аргументам вызывает временную материализацию и дополнительную конструкцию перемещения / копирования:

struct heavy{
   unsigned char[4096];
   heavy(const heavy&);
};
heavy make_heavy(); // Return a pr-value
auto loc = ::operator new(sizeof(heavy));
// Equivalently: unsigned char loc[sizeof(heavy)];

auto p = construct<heavy>(loc,make_heavy()); // The pr-value returned by
         // make_heavy is bound to the second argument,
         // and then this arugment is copied in the body of construct.

auto p2 = new(loc) auto(make_heavy()); // Heavy is directly constructed at loc
       //... and this is simpler to write!

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

С другой стороны, construct_at в библиотеке может завершить стандартный словарь библиотеки.

0 голосов
/ 24 октября 2018

Есть std::allocator_traits::construct.Раньше был еще один в std::allocator, но он был удален, обоснование в документе комитета по стандартам D0174R0 .

0 голосов
/ 24 октября 2018

Есть такая вещь, но не назван так, как вы могли бы ожидать :

  • uninitialized_copy копирует диапазон объектов в неинициализированныйобласть памяти

  • uninitialized_copy_n (C ++ 11) копирует несколько объектов в неинициализированную область памяти (шаблон функции)

  • uninitialized_fill копирует объект в неинициализированную область памяти, определяемую диапазоном (шаблон функции)

  • uninitialized_fill_n копирует объект внеинициализированная область памяти, определяемая началом и счетчиком (шаблон функции)
  • uninitialized_move (C ++ 17) перемещает диапазон объектов в неинициализированную область памяти (шаблон функции)
  • uninitialized_move_n (C ++ 17) перемещает несколько объектов в неинициализированную область памяти (шаблон функции)
  • uninitialized_default_construct (C++ 17) конструирует объекты по умолчаниюlt-инициализация в неинициализированной области памяти, определенной диапазоном (шаблон функции)
  • uninitialized_default_construct_n (C ++ 17), создает объекты по умолчанию-инициализация в неинициализированной области памяти,определяется начальным значением и счетчиком (шаблон функции)
  • uninitialized_value_construct (C ++ 17) создает объекты путем инициализации значения в неинициализированной области памяти, определенной диапазоном (шаблон функции)
  • uninitialized_value_construct_n (C ++ 17) создает объекты путем инициализации значения в неинициализированной области памяти, определяемой запуском и числом
...