Elenco dinâmico no destruidor

8

Esse código é legal?

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

Vejo as duas impressões, mas devo ver apenas uma. Eu acho que o elenco dinâmico está errado. É possível fazer uma verificação desse tipo?

greywolf82
fonte
Você pode esclarecer o que está tentando verificar? O Base2 deseja saber se é uma base de uma classe derivada que também possui um Base1?
jtbandes 29/01
Sim, quero fazer check-in na Base2 se "this" também é filho da base1
greywolf82 em
"mas eu deveria ver apenas um" por quê? e por que você tem dúvidas sobre a legalidade?
idclev 463035818 29/01
1
@ greywolf82 oh desculpe, eu perdi o!
idclev 463035818 29/01
2
Não se dynamic_castcomporta de maneira diferente em construtores e destruidores?
François Andrieux 29/01

Respostas:

7

Talvez eu tenha encontrado a solução, a resposta é não, não é possível:

Do ponto 6 da documentação do cppreference.com :

Quando dynamic_cast é usado em um construtor ou destruidor (direta ou indiretamente) e expressão refere-se ao objeto que está atualmente em construção / destruição, o objeto é considerado o objeto mais derivado. Se new-type não for um ponteiro ou uma referência à própria classe do construtor / destruidor ou a uma de suas bases, o comportamento será indefinido.

Veja também [class.cdtor] / 6 da norma.

Desde que estou transmitindo para Base1 no destruidor Base2, esse comportamento é indefinido.

greywolf82
fonte
1
[class.cdtor]/6, para referência. Desculpe, não 5. Era 5 em C ++ 17 (rascunho N4659), parece que é /6agora.
ChrisMM 29/01
@ChrisMM Esse parágrafo não parece mencionar o comportamento indefinido mencionado nesta resposta. Na verdade, não consegui encontrar essa restrição em nenhum lugar do padrão.
noz
Eu acho que a cppreference erroneamente escreveu " new-type " quando deveria ter sido " expression ". A passagem padrão vinculada diz que o operando precisa ter o tipo (estático) da classe do destruidor ou uma base dela se se referir ao objeto que está sendo destruído. No código da pergunta thisé do tipo do destruidor, então não vejo esse UB sendo aplicado. O mesmo parágrafo padrão diz, no entanto, que o tipo mais derivado do objeto sob destruição será considerado a classe do destruidor, de modo que o comportamento explicado na outra resposta seja observado.
noz
@ Walnut, estava apenas postando a passagem relevante do padrão, não a minha resposta. Concordo que não é UB embora.
ChrisMM 30/01
3

Concordo com a resposta do @ j6t, mas aqui está um raciocínio expandido com referências padrão.

O comportamento especial de dynamic_castobjetos em construção e destruição é descrito por [class.cdtor] / 5 do padrão C ++ 17 (rascunho final) e equivalentemente pelas versões padrão anteriores.

Em particular, diz:

Quando a dynamic_­casté usado em um destruidor, [...] se o operando de dynamic_­castse refere ao objeto em construção ou destruição, esse objeto é considerado o objeto mais derivado que possui o tipo de [ classe do destruidor. Se o operando do dynamic_­castrefere-se ao objeto em [...] destruição e o tipo estático do operando não é um ponteiro ou objeto da [...] própria classe do destruidor ou de uma de suas bases, o dynamic_cast resulta em comportamento indefinido.

O comportamento indefinido não se aplica aqui, pois o operando é a expressão this, que trivialmente tem o tipo de um ponteiro para a própria classe do destruidor, uma vez que aparece no próprio destruidor.

No entanto, a primeira frase afirma que o dynamic_castcomportamento se comportará como se *thisfosse um objeto do tipo mais derivado Base2e, portanto, o elenco a Base1que nunca será bem-sucedido, porque Base2não é derivado de Base1, e dynamic_cast<Base1*>(this)sempre retornará um ponteiro nulo, resultando no comportamento que você está vendo.


cppreference.com afirma que o comportamento indefinido ocorre se o tipo de destino da conversão não for o tipo da classe do destruidor ou uma de suas bases, em vez de aplicar isso ao tipo de operandos. Eu acho que isso é apenas um erro. Provavelmente, a menção de " novo tipo " no ponto 6 da bala deveria dizer " expressão ", o que faria corresponder à minha interpretação acima.

noz
fonte
2

O dynamic_castestá bem definido nessa situação. É correto que você observe as duas linhas de saída.

Você está errado ao supor que no destruidor de Base2 thisé uma classe derivada. No momento, a parte da classe derivada já foi destruída, portanto, não pode mais ser uma classe derivada. De fato, no momento em que o destruidor de Base2execuções, o objeto apontado por this é apenas um Base2objeto. Como Base2não está relacionado de Base1forma alguma, o dynamic_castretorna um ponteiro nulo e o condicional é inserido de acordo.

Edit: O padrão diz :

Quando a dynamic_­casté usado em um construtor [...] ou em um destruidor, [...] se o operando do dynamic_­castreferir-se ao objeto em construção ou destruição, esse objeto é considerado o objeto mais derivado que possui o tipo da classe do construtor ou destruidor. Se o operando do dynamic_­castrefere-se ao objeto em construção ou destruição e o tipo estático do operando não é um ponteiro ou objeto da própria classe do construtor ou destruidor ou de uma de suas bases, o dynamic_­castresultado é um comportamento indefinido.

O operando thisrefere-se ao objeto que está sendo destruído. Portanto, a classe do destructor ( Base2) é considerada a classe mais derivada, e esse é o motivo pelo qual o objeto não está relacionado ao tipo de destino ( Base1*) de nenhuma maneira. Além disso, o tipo estático do operando thisé Base2* const, o que claramente é um ponteiro para a própria classe do destruidor. Portanto, a regra sobre comportamento indefinido não se aplica. Em resumo, temos um comportamento bem definido.

j6t
fonte
1
isso está contradizendo a outra resposta que afirma (citando o padrão) que o código tem um comportamento indefinido. Você precisa de alguns bons argumentos para contestar isso, apenas dizendo
idclev 463035818 29/01
1
@ formerlyknownas_463035818 A outra resposta está citando cppreference, não o padrão. Isso pode não ter sido claro. O parágrafo padrão mencionado não parece dizer a mesma coisa que cppreference.
noz
@ Walnut eu pensei que era uma citação do padrão, enquanto isso foi editado para citar de cppref. De qualquer forma, as respostas são contraditórias, uma tem fontes para fazer backup, a outra não, apenas dizendo ...
idclev 463035818 30/01
@walnut, verifiquei a mim mesmo e [class.cdtor]/6mencionei nas outras respostas o mesmo que cppref: "Se o operando do dynamic_cast se referir ao objeto em construção ou destruição e o tipo estático do operando não for um ponteiro ou objeto do construtor ou própria classe do destruidor ou uma de suas bases, o dynamic_cast resulta em um comportamento indefinido ".
idclev 463035818 em 30/01
1
Eu adicionei minha interpretação do padrão.
j6t 30/01