Mi clase hereda de múltiples bases, una de las cuales es std::enable_shared_from_this
. ¿Debe ser la primera base?
Supongamos el siguiente código de ejemplo:
struct A { ~A(); };
struct B { ~B(); };
struct C : A, B, std::enable_shared_from_this<C> {};
std::make_shared<C>();
Cuándo ~A()
y ~B()
ejecutar, ¿puedo estar seguro de que el almacenamiento donde C
vivía todavía está presente?
std::enable_shared_from_this
no hace mucho. Su ejemplo se ve bien para mí (suponiendo que no está tratando de hacer algo inteligente en~A
y~B
, al igual que hacia abajo de fundición a presiónthis
aC*
)enable_shared_from_this
debe ser una base accesible e inequívoca. En mi ejemplo, lo es.C
es una estructura Hereda públicamente.class
ypublic
. Elegístruct
el ejemplo para evitar escribir.~weak_ptr();
Efectos: destruye esteweak_ptr
objeto pero no tiene ningún efecto sobre el objeto al que apunta su puntero almacenado". El énfasis es mío.shared_ptr
muere. Incluso siweak_ptr
evita que el bloque de control sea desasignado, no creo que importe.Respuestas:
¡Por supuesto! Sería difícil usar una clase base que intente liberar su propia memoria (la memoria donde reside). No estoy seguro de que sea formalmente legal.
Las implementaciones no hacen eso: cuando
shared_ptr<T>
se destruye o restablece a, el recuento de referencia (RC) para la propiedad compartida deT
disminuye (atómicamente); si alcanzó 0 en la disminución,T
se inicia la destrucción / eliminación de .Luego, el conteo de propietarios débiles o T existe se disminuye (atómicamente), ya que
T
ya no existe: necesitamos saber si somos la última entidad interesada en el bloque de control; si la disminución dio un resultado distinto de cero, significaweak_ptr
que existen algunos que comparten (podría ser 1 acción o 100%) la propiedad del bloque de control, y ahora son responsables de la desasignación.De cualquier manera, la disminución atómica terminará en algún momento con un valor cero, para el último copropietario.
Aquí no hay hilos, no no determinismo, y obviamente el último
weak_ptr<T>
fue destruido durante la destrucción deC
. (La suposición no escrita en su pregunta es que noweak_ptr<T>
se guardó ninguna otra ).La destrucción siempre ocurre en ese orden exacto . El bloque de control se usa para la destrucción, ya que no se
shared_ptr<T>
sabe (en general) qué destructor (potencialmente no virtual) de la clase más derivada (potencialmente diferente) para llamar . (El bloque de control también sabe que no se debe desasignar la memoria en el recuento compartido que llega a ceromake_shared
).La única variación práctica entre las implementaciones parece ser sobre los detalles finos de las vallas de memoria y evitar algunas operaciones atómicas en casos comunes.
fuente
weak_count
1 para un objeto que semake_shared
editó incluso si no hayweak_ptr
s. Liberando solo losshared_ptr
primeros decretosuse_count
. Si se convirtió en 0, el objeto (pero no el bloque de control) se destruye. Luegoweak_count
se disminuye, y si 0 el bloque de control se destruye + libera. Un objeto que hereda deenable_shared_from_this
comienza conweak_count
= 2. Una solución brillante por parte de los implementadores de STL, como se esperaba.random_access_iterator_tag
.). Existe un acuerdo informal para llamar a todo lo relacionado con contenedores como parte del STL. tl; dr: no todas las plantillas en la biblioteca estándar son parte del STL y no todas las plantillas que no están fuera de él.No, y el orden de las clases base es irrelevante. Incluso el uso (o no) de enable_shared_from_this es irrelevante.
Cuando se destruye un objeto C (sin importar lo que ocurra),
~C()
se llamará antes que ambos~A()
y~B()
, ya que esa es la forma en que funcionan los destructores de bases. Si intenta "reconstruir" el objeto C en cualquier destructor base y acceder a los campos en él, esos campos ya se habrán destruido, por lo que obtendrá un comportamiento indefinido.fuente
enable_shared_from_this
puede aparecer en cualquier lugar de la lista base, se requieren implementaciones para liberar memoria después de la destrucción de todo el objeto, sin importar cómo se hereda deenable_shared_from_this
", o "It debe ser la primera base, heredar en cualquier otro lugar es UB ", o" Este comportamiento no está especificado, o la calidad de la implementación ".Si crea un objeto c de tipo C, con bases A, B y un contador de referencia heredando de la base
enable_shared_from_this<T>
, en primer lugar se asigna memoria para todo el objeto resultante, incluidas las bases en general y la baseenable_shared_from_this<T>
. El objeto no se destruirá hasta que el último propietario (también conocido como shared_ptr) renuncie a la propiedad. En ese momento ~ enable_shared ..., ~ B y ~ A se ejecutarán después de ~ C. Todavía se garantiza que la memoria asignada completa estará allí hasta que se ejecute el último destructor ~ A. Después de ejecutar ~ A, la memoria completa del objeto se libera de una sola vez. Entonces para responder a su pregunta:Sí, aunque no puede acceder legalmente a él, pero ¿por qué necesitaría saberlo? ¿Qué problema estás tratando de evitar?
fuente
shared_ptr
,weak_ptr
yenable_shared_from_this
son necesarias para mantener la memoria el tiempo suficiente para que esto sea seguro, incluso cuandoenable_shared_from_this
no es la primera base.enable_shared_from_this
después de otra clase base.