Reparto dinámico en destructor

8

¿Es legal este código?

class Base1 {
};

class Base2 {
public:
    virtual ~Base2() {
        if (!dynamic_cast<Base1*>(this))
            std::cout << "aaaa" << std::endl;
    }
    Base2() {
    }
};

class MyClass: public Base1, public Base2 {
public:
    MyClass() {
    }
    virtual ~MyClass() {
        std::cout << "bbb" << std::endl;
    }
};

int main() {
    MyClass s;
    return 0;
}

Veo ambas impresiones pero debería ver solo una. Supongo que el elenco dinámico está mal. ¿Es posible hacer un control de este tipo?

greywolf82
fuente
¿Puedes aclarar lo que estás tratando de verificar? ¿La Base2 quiere saber si es una base de una clase derivada que también tiene una Base1?
jtbandes
Sí, quiero comprobar en Base2 si "esto" también es hijo de base1
greywolf82
"pero debería ver solo uno" ¿por qué? ¿Y por qué tienes dudas sobre la legalidad?
idclev 463035818
1
@ greywolf82 oh lo siento, me perdí el!
idclev 463035818
2
¿No se dynamic_castcomporta de manera diferente en constructores y destructores?
François Andrieux

Respuestas:

7

Tal vez encontré la solución yo mismo, la respuesta es no, no es posible:

De la viñeta 6 de la documentación de cppreference.com :

Cuando dynamic_cast se usa en un constructor o un destructor (directa o indirectamente), y la expresión se refiere al objeto que está actualmente en construcción / destrucción, el objeto se considera el objeto más derivado. Si new-type no es un puntero o una referencia a la propia clase del constructor / destructor o una de sus bases, el comportamiento es indefinido.

Ver también [class.cdtor] / 6 del estándar.

Como estoy enviando a Base1 en el destructor de Base2, este comportamiento no está definido.

greywolf82
fuente
1
[class.cdtor]/6, para referencia. Lo sentimos, no 5. Era 5 en C ++ 17 (borrador N4659), parece que es /6ahora.
ChrisMM
@ChrisMM Ese párrafo no parece mencionar el comportamiento indefinido al que se hace referencia en esta respuesta. En realidad, no pude encontrar esta restricción en ninguna parte del estándar.
nogal
Creo que cppreference escribió erróneamente " new-type " cuando debería haber sido " expresión ". El pasaje estándar vinculado dice que el operando debe tener un tipo (estático) de la clase del destructor o una base de la misma si se refiere al objeto bajo destrucción. En el código de la pregunta thises del tipo del destructor, por lo que no veo que UB se aplique. Sin embargo, el mismo párrafo estándar dice que el tipo más derivado del objeto bajo destrucción se considerará la clase del destructor, de modo que se observa el comportamiento explicado en la otra respuesta.
nogal
@walnut, solo estaba publicando el pasaje correspondiente del estándar, no mi respuesta. Sin embargo, estoy de acuerdo que no es UB.
ChrisMM
3

Estoy de acuerdo con la respuesta de @ j6t, pero aquí hay un razonamiento ampliado con referencias estándar.

El comportamiento especial de los dynamic_castobjetos en construcción y destrucción se describe en [class.cdtor] / 5 del estándar C ++ 17 (borrador final) y de manera equivalente en las versiones estándar anteriores.

En particular dice:

Cuando dynamic_­castse utiliza [...] en un destructor, [...], si el operando del se dynamic_­castrefiere al objeto en construcción o destrucción, este objeto se considera el más derivado que tiene el tipo de [ ...] clase de destructor. Si el operando del se dynamic_­castrefiere al objeto bajo [...] destrucción y el tipo estático del operando no es un puntero u objeto de la propia clase [...] del destructor o una de sus bases, el resultado de dynamic_cast comportamiento indefinido

El comportamiento indefinido no se aplica aquí, ya que el operando es la expresión this, que trivialmente tiene el tipo de un puntero a la propia clase del destructor, ya que aparece en el destructor mismo.

Sin embargo, la primera oración establece que dynamic_castse comportará como si *thisfuera un objeto de tipo más derivado Base2y, por lo tanto, la conversión a Base1nunca puede tener éxito, ya Base2que no se deriva de Base1, y dynamic_cast<Base1*>(this)siempre devolverá un puntero nulo, resultando en el comportamiento que está viendo.


cppreference.com afirma que el comportamiento indefinido ocurre si el tipo de destino del molde no es el tipo de la clase del destructor o una de sus bases, en lugar de que esto se aplique al tipo de operandos. Creo que eso es solo un error. Probablemente, la mención de " nuevo tipo " en el punto 6 se suponía que debía decir " expresión ", lo que haría que coincidiera con mi interpretación anterior.

nuez
fuente
2

El dynamic_castestá bien definido en esta situación. Es correcto que observe ambas líneas de salida.

Se equivoca al suponer que en el destructor de Base2 thises una clase derivada. En este momento, la parte de la clase derivada ya se ha destruido, por lo que ya no puede ser una clase derivada. De hecho, en el momento en que se Base2ejecuta el destructor de , el objeto señalado por this es solo un Base2objeto. Como Base2no está relacionado de Base1ninguna manera, dynamic_castdevuelve un puntero nulo y el condicional se ingresa en consecuencia.

Editar: El estándar dice :

Cuando a dynamic_­castse utiliza en un constructor [...] o en un destructor [...], si el operando del se dynamic_­castrefiere al objeto en construcción o destrucción, este objeto se considera el objeto más derivado que tiene el tipo de la clase del constructor o destructor. Si el operando del se dynamic_­castrefiere al objeto en construcción o destrucción y el tipo estático del operando no es un puntero u objeto de la propia clase del constructor o destructor o una de sus bases, el dynamic_­castresultado es un comportamiento indefinido.

El operando se thisrefiere al objeto bajo destrucción. Por lo tanto, la clase del destructor ( Base2) se considera la clase más derivada, y esa es la razón por la cual el objeto no está relacionado con el tipo de destino ( Base1*) de ninguna manera. Además, el tipo estático del operando thises Base2* const, que claramente es un puntero a la propia clase del destructor. Por lo tanto, la regla sobre el comportamiento indefinido no se aplica. En resumen, tenemos un comportamiento bien definido.

j6t
fuente
1
Esto está contradiciendo la otra respuesta que establece (citando el estándar) que el código tiene un comportamiento indefinido. Necesitas algunos buenos argumentos para objetar eso, solo diciendo
idclev 463035818
1
@ formerlyknownas_463035818 La otra respuesta es una cita de cppreference, no el estándar. Eso podría no haber sido claro. El párrafo estándar que menciona no parece decir lo mismo que cppreference.
nogal
@walnut Pensé que era una cita del estándar mientras que fue editado para citar desde cppref. De todos modos, las respuestas son contradictorias, uno tiene fuentes para respaldarlo, el otro no, solo dice ...
idclev 463035818
@walnut Me revisé y [class.cdtor]/6mencioné en la otra respuesta que dice lo mismo que cppref: "Si el operando de dynamic_cast se refiere al objeto en construcción o destrucción y el tipo estático del operando no es un puntero u objeto del constructor o propia clase de destructor o una de sus bases, el resultado de dynamic_cast en un comportamiento indefinido ".
idclev 463035818
1
He agregado mi interpretación del estándar.
j6t