tipo informacion for evolucion descargar datos creador c++ c++17 placement-new

informacion - for c++ 17



¿Por qué no hay un std:: construct_at en C++ 17? (6)

Creo que debería haber una función constructiva estándar. De hecho, libc ++ tiene uno como detalle de implementación en el archivo 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)...); } ... }

Creo que es algo útil tener porque permite hacer de la "nueva ubicación" un amigo. Este es un gran punto de personalización para un tipo de solo movimiento que necesita una uninitialized_copy en el montón predeterminado (desde un elemento std::initializer_list , por ejemplo).

Tengo mi propia biblioteca de contenedores que reimplementa un detail::uninitialized_copy (de un rango) para usar un detail::construct personalizado 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)...); } }

Que se declara amigo de una clase de solo movimiento para permitir la copia solo en el contexto de la ubicación nueva.

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; ... };

C ++ 17 agrega std::destroy_at , pero no hay ninguna contrapartida de std::construct_at . ¿Porqué es eso? ¿No podría ser implementado de la siguiente manera?

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

Lo que permitiría evitar esa nueva sintaxis de colocación no completamente natural :

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


Existe tal cosa, pero no se nombra como se podría esperar :

  • uninitialized_copy copia un rango de objetos a un área de memoria no inicializada

  • uninitialized_copy_n (C ++ 11) copia varios objetos a un área de memoria no inicializada (plantilla de función)

  • uninitialized_fill copia un objeto en un área de memoria no inicializada, definida por un rango (plantilla de función)

  • uninitialized_fill_n copia un objeto en un área de memoria no inicializada, definida por un inicio y un conteo (plantilla de función)
  • uninitialized_move (C ++ 17) mueve un rango de objetos a un área sin inicializar de la memoria (plantilla de función)
  • uninitialized_move_n (C ++ 17) mueve una serie de objetos a un área de memoria no inicializada (plantilla de función)
  • uninitialized_default_construct (C ++ 17) construye objetos por inicialización predeterminada en un área de memoria no inicializada, definida por un rango (plantilla de función)
  • uninitialized_default_construct_n (C ++ 17) construye objetos por inicialización predeterminada en un área de memoria no inicializada, definida por un inicio y un recuento (plantilla de función)
  • uninitialized_value_construct (C ++ 17) construye objetos por inicialización de valores en un área de memoria no inicializada, definida por un rango (plantilla de función)
  • uninitialized_value_construct_n (C ++ 17) construye objetos por inicialización de valores en un área de memoria no inicializada, definida por un inicio y un conteo


construct no parece proporcionar ningún azúcar sintáctico. Además es menos eficiente que una colocación nueva. Los enlaces a los argumentos de referencia causan materialización temporal y construcción adicional de movimiento / copia:

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!

Desafortunadamente, no hay ninguna forma de evitar estas construcciones adicionales de copia / movimiento al llamar a una función. El reenvío es casi perfecto.

Por otro lado, construct_at en la biblioteca podría completar el vocabulario estándar de la biblioteca.


std::construct_at se ha agregado a C ++ 20. El papel que lo hizo es contenedores más constexpr . Presumiblemente, no se vio que esto tuviera suficientes ventajas sobre la ubicación nueva en C ++ 17, pero C ++ 20 cambia las cosas.

El propósito de la propuesta que agregó esta función es admitir las asignaciones de memoria constexpr, incluido std::vector . Esto requiere la capacidad de construir objetos en el almacenamiento asignado. Sin embargo, simplemente coloque nuevas ofertas en términos de void * , no T * . constexpr evaluación constexpr no tiene capacidad para acceder al almacenamiento sin constexpr , y el comité quiere mantenerlo así. La función de biblioteca std::construct_at agrega una interfaz escrita constexpr T * construct_at(T *, Args && ...) .

Esto también tiene la ventaja de no requerir que el usuario especifique el tipo que se está construyendo; Se deduce del tipo de puntero. La sintaxis para llamar correctamente a la ubicación nueva es algo horrible y contraintuitiva. Compare std::construct_at(ptr, args...) con ::new(static_cast<void *>(ptr)) std::decay_t<decltype(*ptr)>(args...) .


std::destroy_at proporciona dos mejoras objetivas sobre una llamada de destructor directo:

  1. Reduce la redundancia:

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

    Claro, todos preferiríamos simplemente envolverlo en un unique_ptr y terminar con él, pero si eso no puede suceder por alguna razón, poner T allí es un elemento de redundancia. Si cambiamos el tipo a U , ahora tenemos que cambiar la llamada del destructor o las cosas se rompen. El uso de std::destroy_at(ptr) elimina la necesidad de cambiar lo mismo en dos lugares.

    Lo seco es bueno.

  2. Esto lo hace fácil:

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

    Si deducimos el tipo de puntero, borrarlo se vuelve un poco difícil. No puedes hacer ptr->~decltype(ptr)() ; ya que el analizador de C ++ no funciona de esa manera. No solo eso, decltype deduce el tipo como un puntero , por lo que debería eliminar un direccionamiento indirecto del puntero del tipo deducido. Guiándote a

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

    ¿Y quién quiere escribir eso ?

Por el contrario, su hipotético std::construct_at no proporciona mejoras objetivas sobre la ubicación new . Tienes que indicar el tipo que estás creando en ambos casos. Los parámetros al constructor deben ser provistos en ambos casos. El puntero a la memoria debe proporcionarse en ambos casos.

Así que no hay necesidad de ser resuelto por su hipotético std::construct_at .

Y es objetivamente menos capaz que la colocación nueva. Puedes hacerlo:

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

Estos son diferentes En el primer caso, el objeto está inicializado por defecto, lo que puede dejarlo sin inicializar. En el segundo caso, el objeto está inicializado por valores.

Su hipotético std::construct_at no puede permitirle elegir cuál desea. Puede tener un código que realice la inicialización predeterminada si no proporciona parámetros, pero no podrá proporcionar una versión para la inicialización de valores. Y se podría inicializar el valor sin parámetros, pero luego no se pudo inicializar el objeto por defecto.