dynamic_cast and abstract class(Interface)

Started by
9 comments, last by exOfde 10 years, 4 months ago

Hello,

few minutes ago i tried to cast dynamically one class into an other class. To be more specific:

I have two Interface. The first Interface inherited to the other one and the second Interface inherited to the two different classes let's say A and B. The second Interface provides one full virtual member function.

The Interfaces:


class Interface
{
public:
   Interface(){}
}

class fooInterface
{
public:
    fooInterface(){}

    virtual int type() =0;
}


The concrete classes A and B:


class A : public fooInterface
{
public:

virtual int type(){return 1;}
virtual void foo(A var){std::cerr<<var.type(); }
}

class B : public fooInterface
{
public:

virtual int type(){return 2;}
virtual void foo(B var){std::cerr<<var.type(); }
}

The main function:


int main()
{
   A TestOne;
   B TestTwo;

   TestOne.foo(*(dynamic_cast<A*>(&TestTwo)));
   TestTwo.foo(*(dynamic_cast<B*>(&TestOne)));

   return 0;

} 

The Output is this:

1

2

Easy to understand and it's buildable. But the compiler give me a warning:

warning: dynamic_cast of 'B TestTwo' to 'class A*' can never succeed [enabled by default]

warning: dynamic_cast of 'A TestOne' to 'class B*' can never succeed [enabled by default]

The problem now is why give me the compiler this warning which says it would be never succed but the programm don't crash here? I came up with this question because i am still on a "bigger" project where i have the same problematic. That is also the reason why i use these two interface classes. the first one is not really in use here in this example, but that's in differen topic.

And I thought before i ask for help for the problem there i want first understand this problem here in generall.

Advertisement

You need to cast the address not the object?

i.e.

dynamic_cast<A*>(&TestTwo)

I don't think you know what is actually happening though, a dynamic_cast will just return 0 (or nullptr) if the cast can't be done.

And the reason the program behaves as you expect is because when you use *(some pointer to A) to call a function it copies the A part of the object (i.e. slices the object if it is a B) using the copy constructor and that actually is an A so will have A's virtual function table.

EDIT: I see B is not actually derived from A, so the dynamic cast of a B* to an A* should fail with a nullptr and likely crash? I don't know what the compiler actually did in your case to make it seem to work?

"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley

Yes and that is what i don't understand. I looked already with google for some solutions and what i found

was it should crash this way.

But my compiler g++ give me only the warning, build it and i can run the program. what therotically should not

be possible, shouldn't? But depend on the output plus what i saw with the debugger was that g++ just the original function of Object A and B are called.

Because like you said dynamic_cast should return nullptr.

Yeah I dunno why it worked either. Haven't got gcc to check. You should really turn warnings up to max level and treat warnings as errors anyway, in my opinion.

"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley
This is a nasty corner case in C++. I'm not enough of a language lawyer to tell you if this is strictly undefined behavior or not, but it smells like it.

Here's what's happening, breaking it down bit by bit:

   TestOne.foo(*(dynamic_cast<A*>(&TestTwo)));
   TestTwo.foo(*(dynamic_cast<B*>(&TestOne)));
First we take the address of TestTwo. This is valid.

Then we dynamic_cast to an A*. This is NOT valid and returns nullptr. Store the result of the cast in a temporary A* variable and observe in the debugger if you don't believe me.

Then we construct a temporary object of type A by copying from nullptr. This doesn't crash because the constructor is trivial and doesn't attempt to access the memory held by the object being copied, i.e. nullptr is never dereferenced. Add a nontrivial copy constructor and you will get a crash.

Next, we pass this temporary object - located on the stack, so *not* nullptr - to the foo function. Since it is passed by value, the temporary has a valid vtable by definition.

The temporary object of type A has a valid vtable, so when we go to invoke the GetType function, we will land in the correct code. Note however that GetType()'s implementation never accesses the this pointer, so it will never dereference nullptr. Add some code that interacts with the object or otherwise dereferences this, and you will get a crash.

GetType does not crash, and simply returns the expected value. Repeat this process for type B instead of A, and you get the OP's described results.


Another way to get this to crash as expected is to pass by reference (or const reference) instead of passing by value. Even passing a pointer instead of a value will crash.


Essentially, you've been bitten by pass-by-value semantics.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

So presumably gcc is actually taking the address and warning about it rather than erroring out since the OP code wasn't taking the address. I think MSVC will give an error for that.

I agree with everything you say about why it works though (i.e. constructs a copy from a nullptr with a valid virtual function table, any attempt to dereference the this pointer in the function will crash).

"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley
VS2010 happily compiles and runs the OP's code without any errors or warnings on the default settings.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Ah I see he edited it ;) Originally it didn't have the address.

"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley

Thank you for this explanation, it helped me a lot to understand. Your presumption of the dynamic_cast on *A are right, and i believe you this ;) But your presumption that if i try to access the this pointer in the type() function seems to be wrong.

I tried in this way:


class B : public SecondStage
{
    //int b;
public:
    B(){ }
    
    virtual void boo(){ int a = 5+9; std::cerr<<"5+9 = "<<a<<"\n";}
    virtual int type()
{
this->boo();
return 2;
}
    
    virtual void foo(B var){ std::cerr<<var.type()<<"\n";}
};

And no error appears or crash of the program. But if i put a member value in the class after that point it starts to crash and behiave like you predicted it.

Sorry, I wasn't clear. You're not actually accessing the nullptr "this" in that code - you're still accessing the temporary by-value object's "this." So you're right, it shouldn't crash. You need to do something that causes the nullptr to be dereferenced to get a crash, such as writing a copy constructor or adding member data to the object.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

This topic is closed to new replies.

Advertisement