smart programming pointer c++ destructor smart-pointers

programming - smart pointer c++



shared_ptr magic:) (3)

El Sr. Lidström y yo tuvimos una discusión :)

El reclamo del Sr. Lidström es que una construcción shared_ptr<Base> p(new Derived); no requiere que Base tenga un destructor virtual:

Armen Tsirunyan : "¿En serio? ¿Se limpiará el shared_ptr correctamente? ¿Podrías por favor en este caso demostrar cómo se podría implementar ese efecto?"

Daniel Lidström : " shared_ptr usa su propio destructor para eliminar la instancia de Concrete. Esto se conoce como RAII dentro de la comunidad de C ++. Mi consejo es que aprendan todo lo que puedan sobre RAII. Harán que su codificación de C ++ sea mucho más fácil cuando usen RAII en todas las situaciones ".

Armen Tsirunyan : "Sé sobre RAII, y también sé que eventualmente el destructor shared_ptr puede eliminar el px almacenado cuando pn llega a 0. Pero si px tiene un puntero de tipo estático a Base y un puntero de tipo dinámico a Derived , entonces a menos que Base tenga un virtual destructor, esto dará como resultado un comportamiento indefinido. Corrígeme si estoy equivocado ".

Daniel Lidström : " Shared_ptr sabe que el tipo estático es Concrete. Lo sabe desde que lo aprobé en su constructor. Parece un poco mágico, pero puedo asegurar que es de diseño y extremadamente agradable".

Por lo tanto, juzgarnos. ¿Cómo es posible (si lo es) implementar shared_ptr sin que las clases polimórficas tengan un destructor virtual? Gracias por adelantado


Cuando se crea shared_ptr, almacena un objeto eliminador dentro de sí mismo. Se llama a este objeto cuando shared_ptr está a punto de liberar el recurso apuntado. Como sabes cómo destruir el recurso en el punto de construcción, puedes usar shared_ptr con tipos incompletos. Quien creó el shared_ptr almacenó un eliminador correcto allí.

Por ejemplo, puede crear un eliminador personalizado:

void DeleteDerived(Derived* d) { delete d; } // EDIT: no conversion needed. shared_ptr<Base> p(new Derived, DeleteDerived);

p llamará DeleteDerived para destruir el objeto puntiagudo. La implementación hace esto automáticamente.


Sí, es posible implementar shared_ptr de esa manera. Boost lo hace y el estándar C ++ 11 también requiere este comportamiento. Como una flexibilidad adicional, shared_ptr maneja más que solo un contador de referencia. El llamado eliminador generalmente se coloca en el mismo bloque de memoria que también contiene los contadores de referencia. Pero la parte divertida es que el tipo de este eliminador no es parte del tipo shared_ptr. Esto se llama "borrado de tipo" y es básicamente la misma técnica utilizada para implementar las funciones polimórficas boost :: function o std :: function para ocultar el tipo de functor real. Para que su ejemplo funcione, necesitamos un constructor con plantilla:

template<class T> class shared_ptr { public: ... template<class Y> explicit shared_ptr(Y* p); ... };

Entonces, si usas esto con tus clases Base y Derivadas ...

class Base {}; class Derived : public Base {}; int main() { shared_ptr<Base> sp (new Derived); }

... el constructor con plantilla con Y = Derived se usa para construir el objeto shared_ptr. El constructor tiene así la oportunidad de crear el objeto eliminador apropiado y los contadores de referencia y almacena un puntero a este bloque de control como un miembro de datos. Si el contador de referencia llega a cero, el eliminador previamente creado y con reconocimiento derivado se usará para deshacerse del objeto.

El estándar C ++ 11 tiene lo siguiente que decir acerca de este constructor (20.7.2.2.1):

Requiere: p debe ser convertible a T* . Y será un tipo completo. La expresión delete p estará bien formada, tendrá un comportamiento bien definido y no arrojará excepciones.

Efectos: construye un objeto shared_ptr que posee el puntero p .

...

Y para el destructor (20.7.2.2.2):

Efectos: si *this está vacío o comparte la propiedad con otra instancia use_count() > 1 ), no hay efectos secundarios. De lo contrario, si *this posee un objeto p y un eliminador d , se llama d(p) . De lo contrario, si *this posee un puntero p , y se llama a delete p .

(el énfasis es negrita)


Simplemente,

shared_ptr usa una función de eliminación especial creada por un constructor que siempre usa el destructor del objeto dado y no el destructor de Base, esto es un poco de trabajo con la programación meta de la plantilla, pero funciona.

Algo como eso

template<typename SomeType> shared_ptr(SomeType *p) { this->destroyer = destroyer_function<SomeType>(p); ... }