¿New y delete siguen siendo útiles en C++14?


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

Author: dyp, 2015-06-10

4 answers

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

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

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

Tendrá que usar la ubicación new y, posiblemente, delete. No hay forma de evitarlo.

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

Incluso para los punteros inteligentes estándar, necesitará new si desea usar deleters personalizados, ya que make_unique y make_shared no permiten eso.

 36
Author: Barry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-11 15:21:58

Es una opción relativamente común usar make_unique y make_shared en lugar de llamadas raw 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 estándar (no colocación) new. Se empareja lógicamente con llamar manualmente a un destructor. Estándar new ambos adquieren un de la tienda libre, y construye un objeto en ella. Está emparejado con delete, que destruye el objeto y recicla el almacenamiento en la tienda gratuita. En cierto sentido, standard new llama a placement new internamente, y standard delete llama al destructor internamente.

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

La colocación personalizada new es otra razón para usar new. La colocación personalizada new se puede usar para asignar recursos de un grupo no global allocation asignación de ámbito, o asignación en un proceso compartido cruzado página de memoria, asignación en la tarjeta de vídeo de memoria compartida, etc purposes y otros propósitos. Si desea escribir make_unique_from_custom que asigna su memoria utilizando la colocación personalizada new, tendría que usar la palabra clave new. Custom placement new podría actuar como placement new (en el sentido de que en realidad no adquiere recursos, sino que el recurso se pasa de alguna manera), o podría actuar como standard new (en el sentido de que adquiere recursos, tal vez usando los argumentos pasados).

La colocación personalizada delete es se llama si se lanza una colocación personalizada new, por lo que es posible que tenga que escribir eso. En C++ no llamas a la colocación personalizada delete, sino que (C++) te llama (sobrecarga de r).

Finalmente, make_shared y make_unique son funciones incompletas ya que no admiten deleters personalizados.

Si está escribiendo make_unique_with_deleter, todavía puede usar make_unique para asignar los datos, y .release() a su cuidado único con deleter. Si su deleter quiere meter su estado en el búfer apuntado en lugar de en el unique_ptr o en una asignación separada, tendrá que utilizar la colocación new aquí.

Para make_shared, el código del cliente no tiene acceso al código de creación "stub de recuento de referencia". Por lo que puedo decir, no es fácil que ambos tengan la "asignación combinada de objeto y bloque de conteo de referencia" y un deleter personalizado.

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

En los pocos casos en los que desea llamar a la no colocació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.


He señalado anteriormente que no sabía cómo utilizar un deleter personalizado con un puntero compartido que utiliza un solo bloque de asignación fácilmente. Aquí hay un bosquejo de cómo hacerlo de manera difícil:

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 bastar. Hice un intento de permitir que los deleters sin estado no usen espacio up usando un tuple, pero puede que lo haya fastidiado.

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

 7
Author: Yakk - Adam Nevraumont,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-24 17:02:11

La única razón que se me ocurre es que de vez en cuando es posible que desee utilizar un deleter personalizado con su unique_ptr o shared_ptr. Para usar un deleter 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.

 3
Author: Mark B,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-10 17:52:16

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

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

 1
Author: EdMaster,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-10 18:00:09