El Sr. Lidström y yo tuvimos una discusión :)
La afirmación 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? ¿El shared_ptr se limpiará correctamente? ¿Podrías demostrar en este caso cómo se podría implementar ese efecto?"
Daniel Lidström : "El shared_ptr usa su propio destructor para eliminar la instancia de Concrete. Esto se conoce como RAII dentro de la comunidad C ++. Mi consejo es que aprenda todo lo que pueda sobre RAII. Hará que su codificación C ++ sea mucho más fácil cuando use RAII en todas las situaciones ".
Armen Tsirunyan : "Sé acerca de RAII, y también sé que eventualmente el destructor shared_ptr puede eliminar el px almacenado cuando pn llega a 0. Pero si px tuviera un puntero de tipo estático
Base
y un puntero de tipo dinámicoDerived
, entonces a menos queBase
tenga un destructor virtual, esto resultará en un comportamiento indefinido. Corrígeme si me equivoco ".Daniel Lidström : "El shared_ptr sabe que el tipo estático es Concrete. ¡Lo sabe desde que lo pasé en su constructor! Parece un poco mágico, pero puedo asegurarles que es por diseño y extremadamente agradable".
Entonces, juzgadnos. ¿Cómo es posible (si lo es) implementar shared_ptr sin requerir que las clases polimórficas tengan un destructor virtual? Gracias por adelantado
fuente
shared_ptr<void> p(new Derived)
también destruirá elDerived
objeto por su destructor, sin importar si lo esvirtual
o no.shared_ptr<T>( (T*)new U() )
wherestruct U:T
no hará lo correcto (y esto se puede hacer indirectamente fácilmente, como una función que toma aT*
y se pasa aU*
)Respuestas:
Sí, es posible implementar shared_ptr de esa manera. Boost lo hace y el estándar C ++ 11 también requiere este comportamiento. Como flexibilidad adicional, shared_ptr gestiona más que un contador de referencia. El denominado borrador suele colocarse 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 usa esto con sus clases Base y Derived ...
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. Por tanto, el constructor tiene 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, se utilizará el eliminador creado anteriormente y compatible con Derived para deshacerse del objeto.
El estándar C ++ 11 tiene lo siguiente que decir sobre este constructor (20.7.2.2.1):
Y para el destructor (20.7.2.2.2):
(el énfasis en el uso de fuente en negrita es mío).
fuente
the upcoming standard also requires this behaviour
: (a) ¿Qué norma y (b) puede proporcionar una referencia (a la norma)?add a comment
. En mi opinión, es másBoost does this
quethe Standard requires
. No creo que el Estándar requiera eso por lo que estoy entendiendo. Hablando de ejemplo @sellibitze 'sshared_ptr<Base> sp (new Derived);
, requiere deconstructor
sólo pidedelete Derived
ser bien definido y bien formado. Para la especificación dedestructor
, también hay unp
, pero no creo que se refiera ap
en la especificación deconstructor
.Cuando se crea shared_ptr, almacena un objeto de eliminación dentro de sí mismo. Este objeto se llama cuando shared_ptr está a punto de liberar el recurso señalado. Como sabe cómo destruir el recurso en el punto de construcción, puede usar shared_ptr con tipos incompletos. Quien haya creado 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á a DeleteDerived para destruir el objeto puntiagudo. La implementación hace esto automáticamente.
fuente
shared_ptr
como atributo.Simplemente,
shared_ptr
usa una función de eliminación especial que es 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 meta programación de plantillas, pero funciona.Algo como eso
template<typename SomeType> shared_ptr(SomeType *p) { this->destroyer = destroyer_function<SomeType>(p); ... }
fuente