structur pointer plus new make how explained example array c++ c++11 new-operator c++14 dynamic-memory-allocation

pointer - structur c++



¿Las novedades y eliminar aún son útiles en C++ 14? (4)

Dada la disponibilidad de make_unique y make_shared , así como la eliminación automática por los unique_ptr y shared_ptr , ¿cuáles son las situaciones (además de admitir código heredado) para usar new y delete en C ++ 14?


Diría que la única razón para new y delete es implementar otros tipos de punteros inteligentes.

Por ejemplo, la biblioteca aún no tiene punteros intrusivos como boost :: intrusive_ptr, lo cual es una lástima ya que son superiores por razones de rendimiento a punteros compartidos, como señala Andrei Alexandrescu.


Es una opción relativamente común usar make_unique y make_shared lugar de llamadas sin make_shared a new . Sin embargo, no es obligatorio. Suponiendo que elija seguir esa convención, hay algunos lugares para usar new .

Primero, la colocación no personalizada new (descuidaré la parte "no personalizada", y simplemente la llamaré colocación new ) es un juego de cartas completamente diferente al nuevo estándar (no colocación). Está lógicamente emparejado con llamar manualmente a un destructor. Standard new adquiere un recurso de la tienda gratuita y construye un objeto en él. Se combina con delete , que destruye el objeto y recicla el almacenamiento a la tienda gratuita. En cierto sentido, la colocación de new llamadas estándar es new internamente, y la delete estándar llama internamente al destructor.

La colocación new es la forma en que llama directamente a un constructor en algún almacenamiento, y se requiere para el código de administración avanzada de por vida. Si está implementando una union segura de tipo optional en el almacenamiento alineado o un puntero inteligente (con almacenamiento unificado y vida útil no unificada, como make_shared ), utilizará la ubicación new . Luego, al final de la vida útil de un objeto en particular, llama directamente a su destructor. Al igual que las ubicaciones new y delete ubicación, las llamadas de destructor de ubicación new y manual vienen en pares.

La colocación personalizada new es otra razón para usar new . La ubicación personalizada new se puede utilizar para asignar recursos de un grupo no global (asignación de ámbito o asignación en una página de memoria compartida de proceso cruzado, asignación en memoria compartida de tarjeta de video, etc.) y otros fines. Si desea escribir make_unique_from_custom que asigna su memoria utilizando una ubicación personalizada nueva, deberá usar la new palabra clave. La ubicación personalizada new podría actuar como una ubicación nueva (ya que en realidad no adquiere recursos, sino que el recurso se transfiere de alguna manera), o podría actuar como new estándar (en la medida en que adquiere recursos, tal vez utilizando los argumentos pasados) .

Se llama a la delete ubicación personalizada si una ubicación personalizada arroja new lanzamientos, por lo que es posible que deba escribir eso. En C ++ no se llama delete ubicación personalizada, se llama (C ++) (sobrecarga) .

Finalmente, make_shared y make_unique son funciones incompletas en el sentido de que no admiten borradores personalizados.

Si está escribiendo make_unique_with_deleter , aún puede usar make_unique para asignar los datos y .release() en el cuidado exclusivo de su deletador. Si su eliminador desea rellenar su estado en el búfer apuntado en lugar de en el unique_ptr o en una asignación separada, deberá usar la ubicación new aquí.

Para make_shared , el código del cliente no tiene acceso al código de creación de "código auxiliar de recuento de referencia". Por lo que puedo decir, no es fácil tener la "asignación combinada de objetos y el bloque de recuento de referencias" y un eliminador personalizado.

Además, make_shared hace que la asignación de recursos (el almacenamiento) para el objeto en sí persista mientras persistan los weak_ptr : en algunos casos esto puede no ser deseable, por lo que querrá hacer un shared_ptr<T>(new T(...)) para evitar eso.

En los pocos casos en los que desea llamar a la no ubicación new , puede llamar a make_unique , luego .release() el puntero si desea administrar por separado de ese unique_ptr . Esto aumenta su cobertura RAII de recursos y significa que si hay excepciones u otros errores lógicos, es menos probable que se filtre.

Noté anteriormente que no sabía cómo usar un eliminador personalizado con un puntero compartido que usa un solo bloque de asignación fácilmente. Aquí hay un boceto de cómo hacerlo con engaño:

template<class T, class D> struct custom_delete { std::tuple< std::aligned_storage< sizeof(T), alignof(T) >, D, bool > data; bool bCreated() const { return std::get<2>(data); } void markAsCreated() { std::get<2>()=true; } D&& d()&& { return std::get<1>(std::move(data)); } void* buff() { return &std::get<0>(data); } T* t() { return static_cast<T*>(static_cast<void*>(buff())); } template<class...Ts> explicit custom_delete(Ts...&&ts):data( {},D(std::forward<Ts>(ts)...),false ){} custom_delete(custom_delete&&)=default; ~custom_delete() { if (bCreated()) std::move(*this).d()(t()); } }; template<class T, class D, class...Ts, class dD=std::decay_t<D>> std::shared_ptr<T> make_shared_with_deleter( D&& d, Ts&&... ts ) { auto internal = std::make_shared<custom_delete<T, dD>>(std::forward<D>(d)); if (!internal) return {}; T* r = new(internal->data.buff()) T(std::forward<Ts>(ts...)); internal->markAsCreated(); return { internal, r }; }

Creo que eso debería hacerlo. Intenté permitir que los eliminadores apátridas no usaran espacio usando una tuple , pero es posible que la haya jodido.

En una solución de calidad de biblioteca, si T::T(Ts...) es noexcept , podría eliminar la sobrecarga bCreated , ya que no habría oportunidad de que un custom_delete tenga que ser destruido antes de que se construya la T


La única razón por la que puedo pensar es que ocasionalmente es posible que desee utilizar un eliminador personalizado con su unique_ptr o shared_ptr . Para utilizar un eliminador personalizado, debe crear el puntero inteligente directamente, pasando el resultado de new . Incluso esto no es frecuente, pero aparece en la práctica.

Aparte de eso, parece que make_shared / make_unique debería cubrir casi todos los usos.


Si bien los punteros inteligentes son preferibles a los punteros sin formato en muchos casos, todavía hay muchos casos de uso para new / delete en C ++ 14.

Si necesita escribir algo que requiera construcción en el lugar, por ejemplo:

  • un grupo de memoria
  • un asignador
  • una variante etiquetada
  • mensajes binarios a un búfer

necesitará usar la ubicación new y, posiblemente, delete . No hay forma de evitar eso.

Para algunos contenedores que desea escribir, es posible que desee utilizar punteros sin formato para el almacenamiento.

Incluso para los punteros inteligentes estándar, aún necesitará new si desea usar eliminadores personalizados, ya que make_unique y make_shared no lo permiten.