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.