Advertisement Jump to content
Sign in to follow this  
exOfde

dynamic_cast and abstract class(Interface)

This topic is 1889 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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.

Edited by exOfde

Share this post


Link to post
Share on other sites
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?

Edited by Paradigm Shifter

Share this post


Link to post
Share on other sites

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. 

Edited by exOfde

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites

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).

Share this post


Link to post
Share on other sites

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.

Edited by exOfde

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!