Armen Tsirunyan
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 ptr_compartido limpiar correctamente? ¿Podría, en este caso, demostrar cómo se podría implementar ese efecto?”
Daniel Lidström: “Él ptr_compartido utiliza su propio destructor para eliminar la instancia de Concrete. Esto se conoce como RAII dentro de la comunidad de C++. Mi consejo es que aprendas todo lo que puedas sobre RAII. Hará que su codificación C++ sea mucho más fácil cuando use RAII en todas las situaciones”.
Armen Tsirunyan: “Sé sobre RAII, y también sé que eventualmente el ptr_compartido destructor puede eliminar el px almacenado cuando pn llega a 0. Pero si px tuviera un puntero de tipo estático a
Base
y puntero de tipo dinámico aDerived
entonces a menos queBase
tiene un destructor virtual, esto dará como resultado un comportamiento indefinido. Corrígeme si estoy equivocado.”Daniel Lidström: “Él ptr_compartido sabe que el tipo estático es Concreto. ¡Lo sabe desde que lo pasé en su constructor! Parece un poco mágico, pero les puedo asegurar que es por diseño y extremadamente agradable”.
Entonces, júzguenos. ¿Cómo es posible (si lo es) implementar ptr_compartido sin requerir clases polimórficas para tener destructor virtual?
vender
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 administra más que solo un contador de referencia. El llamado borrador 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 borrador no es parte del tipo shared_ptr. Esto se llama “borrado de tipos” 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 funtor 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 Derived
…
class Base {};
class Derived : public Base {};
int main() {
shared_ptr<Base> sp (new Derived);
}
… el constructor con plantilla con Y=Derived
se utiliza para construir el shared_ptr
objeto. 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 previamente creado y Derived
Se utilizará un eliminador consciente para deshacerse del objeto.
El estándar C++11 dice lo siguiente sobre este constructor (20.7.2.2.1):
Requiere:
p
debe ser convertible aT*
.Y
será un tipo completo. La expresiondelete p
estará bien formado, tendrá un comportamiento bien definido y no arrojará excepciones.Efectos: Construye un
shared_ptr
objeto ese posee el punterop
.…
Y para el destructor (20.7.2.2.2):
Efectos: Si
*this
es vacío o comparte la propiedad con otroshared_ptr
instancia (use_count() > 1
), No hay efectos secundarios. De lo contrario, si*this
posee un objetop
y un eliminadord
,d(p)
se llama.
De lo contrario, si*this
posee un punterop
ydelete p
se llama.
(el énfasis 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
3 oct 2016 a las 9:30
-
Solo quiero agregar un comentario a la respuesta de @sellibitze ya que no tengo suficientes puntos para
add a comment
. OMI, es másBoost does this
quethe Standard requires
. No creo que el Estándar requiera eso por lo que entiendo. Hablando del ejemplo de @sellibitzeshared_ptr<Base> sp (new Derived);
, Requiere deconstructor
solo pidedelete Derived
estar bien definido y bien formado. Para la especificación dedestructor
también hay unap
pero no creo que se refiera a lap
en la especificación deconstructor
.– Lujun Weng
6 de mayo de 2019 a las 5:06
Yakov Galka
Cuando se crea shared_ptr, almacena un borrador objeto dentro de sí mismo. Se llama a este objeto cuando shared_ptr está a punto de liberar el recurso apuntado. Como sabe cómo destruir el recurso en el punto de construcción, puede usar shared_ptr con tipos incompletos. Quienquiera que 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.
-
+1 por el comentario sobre tipos incompletos, muy útil cuando se usa un
shared_ptr
como un atributo.– Matthieu M.
10 de octubre de 2010 a las 10:28
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 metaprogramación de plantilla, pero funciona.
Algo como eso
template<typename SomeType>
shared_ptr(SomeType *p)
{
this->destroyer = destroyer_function<SomeType>(p);
...
}
-
hmm… interesante, estoy empezando a creer esto 🙂
– Armen Tsirunyan
10 de octubre de 2010 a las 9:50
-
@Armen Tsirunyan Debería haber echado un vistazo a la descripción del diseño de shared_ptr antes de comenzar la discusión. Esta ‘captura del eliminador’ es una de las características esenciales de shared_ptr…
– Paul Michalik
10 de octubre de 2010 a las 10:16
-
@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 🙂
– Armen Tsirunyan
10 de octubre de 2010 a las 10:20
-
@Armen Por supuesto que no. Más bien, hizo un buen trabajo al señalar esta característica realmente muy importante de shared_ptr
que con frecuencia es supervisada incluso por desarrolladores experimentados de C++. – Paul Michalik
10 de octubre de 2010 a las 10:42
Otra cosa interesante es que
shared_ptr<void> p(new Derived)
también destruirá elDerived
objeto por su destructor, sin importar si esvirtual
O no.– dalle
10 de octubre de 2010 a las 9:57
Impresionante manera de hacer una pregunta 🙂
– rubenvb
10 de octubre de 2010 a las 11:42
Aunque shared_ptr permite esto, es un muy mala idea para diseñar una clase como base sin un dtor virtual. Los comentarios de Daniel sobre RAII son engañosos, no tiene nada que ver con esto, pero la conversación citada parece una simple falta de comunicación (y una suposición incorrecta de cómo funciona shared_ptr).
– Roger Paté
11/10/2010 a las 18:52
No RAII, sino que borra el tipo del destructor. Hay que tener cuidado, porque
shared_ptr<T>( (T*)new U() )
dóndestruct U:T
no hará lo correcto (y esto se puede hacer indirectamente fácilmente, como una función que toma unT*
y se pasa unU*
)– Yakk – Adam Nevraumont
23 de julio de 2014 a las 0:52
Vaya, esta pregunta se hizo el 10/10/10 a las (casi) 10:00.
– David G.
12 de diciembre de 2017 a las 1:23