Dynamic cast in destructor
Asked Answered
P

3

8

Is this code 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;
}

I see both prints but I should see only one. I guess the dynamic cast is wrong. Is it possible to make a check of this kind?

Peake answered 29/1, 2020 at 18:55 Comment(8)
Can you clarify what you're trying to check? Does the Base2 want to know if it's a base of a derived class that also has a Base1?Alforja
Yes, I want to check in Base2 if "this" is a child of base1 tooPeake
"but I should see only one" why? and why do you have doubts about legality?Bluebill
@formerlyknownas_463035818 MyClass is a child of Base1 and Base2 so when destructor of Base2 is running the dynamic cast should be ok since "this" is a child of base1 too, but maybe I'm wrong it's the reason why I askedPeake
@Peake oh sorry I missed the !Bluebill
Doesn't dynamic_cast behave differently in constructors and destructors?Avellaneda
@FrançoisAndrieux, some people like to live on the edge :)Phlegm
I guess it's legal, I don't see you get arrested for it... If UB is ilegal I'll be in trouble :)Attaboy
P
7

Maybe I found the solution myself, the reply is no it's not possible:

From bullet 6 of cppreference.com documentation:

When dynamic_cast is used in a constructor or a destructor (directly or indirectly), and expression refers to the object that's currently under construction/destruction, the object is considered to be the most derived object. If new-type is not a pointer or reference to the constructor's/destructor's own class or one of its bases, the behavior is undefined.

See also [class.cdtor]/6 of the standard.

Since I'm casting to Base1 in Base2 destructor, this behavior is undefined.

Peake answered 29/1, 2020 at 19:7 Comment(4)
[class.cdtor]/6, for reference. Sorry, not 5. Was 5 in C++17 (draft N4659), looks like it's /6 now.Orchidaceous
@Orchidaceous That paragraph doesn't seem to mention the undefined behavior referenced in this answer. Actually I wasn't able to find this restriction anywhere in the standard.Monolatry
I think cppreference mistakenly wrote "new-type" when it should have been "expression". The linked standard passage does say that the operand needs to have (static) type of the destructor's class or a base thereof if it refers to the object under destruction. In the question's code this is of type of the destructor's, so I don't see that UB applying. The same standard paragraph does however say that the most derived type of the object under destruction is going to be considered to be the class of the destructor, so that the behavior explained in the other answer is observed.Monolatry
@walnut, was just posting the relavant passage from the standard, not my answer. I agree its not UB though.Orchidaceous
M
3

I agree with @j6t's answer, but here is an expanded reasoning with standard references.

The special behavior of dynamic_cast for objects under construction and destruction is described by [class.cdtor]/5 of the C++17 standard (final draft) and equivalently by previous standard versions.

In particular it says:

When a dynamic_­cast is used [...] in a destructor, [...], if the operand of the dynamic_­castrefers to the object under construction or destruction, this object is considered to be a most derived object that has the type of the [...] destructor's class. If the operand of the dynamic_­cast refers to the object under [...] destruction and the static type of the operand is not a pointer to or object of the [...] destructor's own class or one of its bases, the dynamic_­cast results in undefined behavior.

The undefined behavior does not apply here, since the operand is the expression this, which trivially has the type of a pointer to the destructor's own class since it appears in the destructor itself.

However, the first sentence states that the dynamic_cast will behave as if *this was a most derived object of type Base2 and therefore the cast to Base1 can never succeed, because Base2 is not derived from Base1, and dynamic_cast<Base1*>(this) will always return a null pointer, resulting in the behavior you are seeing.


cppreference.com states that the undefined behavior happens if the destination type of the cast is not the type of the destructor's class or one of its bases, rather than having this apply to the operands type. I think that is just a mistake. Probably the mention of "new-type" in bullet point 6 was supposed to say "expression", which would make it match my interpretation above.

Monolatry answered 30/1, 2020 at 9:56 Comment(0)
L
2

The dynamic_cast is well-defined in this situation. It is correct that you observe both lines of output.

You are wrong to assume that in the destructor of Base2 this is a derived class. At this time, the derived class part has already been destroyed, so it cannot be a derived class anymore. In fact, at the time when the destructor of Base2 runs, the object pointed to by this is only a Base2 object. Since Base2 is not related to Base1 in any way, the dynamic_cast returns a null pointer, and the conditional is entered accordingly.

Edit: The standard says:

When a dynamic_­cast is used in a constructor [...] or in a destructor [...], if the operand of the dynamic_­cast refers to the object under construction or destruction, this object is considered to be a most derived object that has the type of the constructor or destructor's class. If the operand of the dynamic_­cast refers to the object under construction or destruction and the static type of the operand is not a pointer to or object of the constructor or destructor's own class or one of its bases, the dynamic_­cast results in undefined behavior.

The operand this refers to the object under destruction. Therefore, the class of the destructor (Base2) is considered the most-derived class, and that is the reason why the object is not related to the destination type (Base1*) in any way. Furthermore, the static type of the operand this is Base2* const, which clearly is a pointer to the destructor's own class. Therefore, the rule about undefined behavior does not apply. In summary, we have well-defined behavior.

Lingerie answered 29/1, 2020 at 19:21 Comment(7)
this is contradicting the other answer which states (quoting from the standard) that the code has undefined behaviour. You need some good arguments to object that, just sayingBluebill
@formerlyknownas_463035818 The other answer is quoting from cppreference, not the standard. That might not have been clear. The standard paragraph it mentions doesn't seem to say the same thing as cppreference.Monolatry
@Monolatry I thought it was a quote from the standard meanwhile it was edited to quote from cppref. Anyhow, the answers are contradicting, one has sources to back it up, the other not, just saying...Bluebill
@Monolatry I checked myself and [class.cdtor]/6 mentioned in the other answer states the same as cppref: "If the operand of the dynamic_­cast refers to the object under construction or destruction and the static type of the operand is not a pointer to or object of the constructor or destructor's own class or one of its bases, the dynamic_­cast results in undefined behavior."Bluebill
@formerlyknownas_463035818 But that is referring to the type of the operand, not the destination type of the cast. I agree that this answer here should cite some sources though. The current explanation isn't really matching the standard language.Monolatry
@Monolatry hm I have to read a bit more. Dont get me wrong, I am not saying that this answer is wrong. I just cannot decide and at first glance and the other answer is slightly more convincing ;)Bluebill
I've added my interpretation of the standard.Lingerie

© 2022 - 2024 — McMap. All rights reserved.