shared_ptr magia :)


89

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 Basey un puntero de tipo dinámico Derived, entonces a menos que Basetenga 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


3
Podrías haberte vinculado al hilo original .
Darin Dimitrov

8
Otra cosa interesante es que shared_ptr<void> p(new Derived)también destruirá el Derivedobjeto por su destructor, sin importar si lo es virtualo no.
dalle

7
Impresionante forma de hacer una pregunta :)
rubenvb 10/10/10

5
Aunque shared_ptr permite esto, es una muy mala idea diseñar una clase como base sin un dtor virtual. Los comentarios de Daniel sobre RAII son engañosos, no tienen nada que ver con esto, pero la conversación citada suena como una simple falta de comunicación (y una suposición incorrecta de cómo funciona shared_ptr).

6
No RAII, sino que borra el destructor. Debe tener cuidado, porque shared_ptr<T>( (T*)new U() )where struct U:Tno hará lo correcto (y esto se puede hacer indirectamente fácilmente, como una función que toma a T*y se pasa a U*)
Yakk - Adam Nevraumont

Respuestas:


74

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):

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

Efectos: construye un shared_ptrobjeto que posee el puntero p.

...

Y para el destructor (20.7.2.2.2):

Efectos: si *thisestá vacío o comparte la propiedad con otra shared_ptrinstancia ( use_count() > 1), no hay efectos secundarios. De lo contrario, si *thisposee un objeto py un eliminador d, d(p)se llama. De lo contrario, si *thisposee un puntero py delete pse llama.

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


the upcoming standard also requires this behaviour: (a) ¿Qué norma y (b) puede proporcionar una referencia (a la norma)?
kevinarpe

Solo quiero agregar un comentario a la respuesta de @sellibitze ya que no tengo suficientes puntos para add a comment. En mi opinión, es más Boost does thisque the Standard requires. No creo que el Estándar requiera eso por lo que estoy entendiendo. Hablando de ejemplo @sellibitze 's shared_ptr<Base> sp (new Derived);, requiere de constructorsólo pide delete Derivedser bien definido y bien formado. Para la especificación de destructor, también hay un p, pero no creo que se refiera a pen la especificación de constructor.
Lujun Weng

28

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.


4
+1 para el comentario sobre tipos incompletos, muy útil cuando se usa a shared_ptrcomo atributo.
Matthieu M.

16

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);
   ...
}

1
hmm ... interesante, estoy empezando a creer esto :)
Armen Tsirunyan

1
@Armen Tsirunyan Deberías haber echado un vistazo a la descripción del diseño de shared_ptr antes de iniciar el debate. Esta 'captura del eliminador' es una de las características esenciales de shared_ptr ...
Paul Michalik

6
@ 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 conocían este hecho sobre shared_ptr. Así que supongo que no fue un gran pecado comenzar este hilo de todos modos :)
Armen Tsirunyan

3
@Armen Por supuesto que no. Por el contrario, hizo un buen trabajo al señalar esta característica realmente muy importante de shared_ptr <T> que con frecuencia es supervisada incluso por desarrolladores de C ++ experimentados.
Paul Michalik
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.