2010-10-10 15 views
73

Mr. Lidström and I had an argumentshared_ptr magia :) :)

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

Armen Tsirunyan:?" Realmente va a la shared_ptr limpiar correctamente? ¿Podrías por favor en este caso demostrar cómo se podría implementar ese efecto?

Daniel Lidström: "El shared_ptr utiliza su propio destructor para eliminar la instancia concreta Esto se conoce como RAII dentro de la ++ comunidad C Mi consejo es que se aprende todo lo que pueda acerca de RAII Se le hará la... La codificación C++ es mucho más fácil cuando usas RAII en todas las situaciones ".

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

Daniel Lidström: "El shared_ptr conoce el tipo estático es de hormigón Se sabe ya lo pasé en su constructor parece un poco como magia, pero puedo asegurar que es por diseño y extremadamente agradable.! "

Así que, juzgános. ¿Cómo es posible (si lo es) implementar shared_ptr sin requerir que las clases polimórficas tengan destructor virtual? Gracias de antemano

+3

Podría haber vinculado al [tema original] (http://stackoverflow.com/questions/3899688/default-virtual-dtor/3899726). –

+0

@Darin: lo hice. – sbi

+7

Otra cosa interesante es que 'shared_ptr p (new Derived)' también destruirá el objeto 'Derived' por su destructor, independientemente de si es' virtual' o no. – dalle

Respuesta

66

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 hacer su trabajo ejemplo, necesitamos un constructor de plantilla:

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

Por lo tanto, si se utiliza este con sus clases base y derivados ...

class Base {}; 
class Derived : public Base {}; 

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

... el constructor de 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 de C++ 11 tiene el siguiente a decir sobre este constructor (20.7.2.2.1):

Requiere:p debe ser convertible en T*. Y debe 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_ptrque posee el puntero p.

...

Y para el destructor (20.7.2.2.2):

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

(el énfasis en negrita es mío).

+0

+1 para que el fragmento de código muestre el borrado de tipo que está sucediendo. – legends2k

+0

'el próximo estándar también requiere este comportamiento': (a) ¿Qué estándar y (b) puede proporcionar una referencia (al estándar)? – kevinarpe

13

Simplemente,

shared_ptr utiliza la función Deleter especial que se crea mediante constructor que siempre utiliza el destructor del objeto dado y no el destructor de base, esto es un poco de trabajo con meta plantilla programación, pero funciona.

Algo así

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

hmm ... interesante, estoy empezando a creer esto :) –

+1

@Armen Tsirunyan Deberías haber echado un vistazo a la descripción del diseño del shared_ptr antes de comenzar la discusson. Esta 'captura del eliminador' es una de las características esenciales de shared_ptr ... –

+5

@ paul_71: estoy de acuerdo contigo. Por otro lado, creo que esta discusión fue útil no solo para mí, sino también para otras personas que no sabían este hecho sobre shared_ptr. Así que supongo que no fue un gran pecado comenzar este hilo de todos modos :) –

26

Cuando se crea shared_ptr que almacena un objetoDeleter 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 Deleter 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.

+4

+1 para la observación sobre tipos incompletos, muy útil cuando se usa 'shared_ptr' como atributo. –