tags:

views:

110

answers:

4

I have the following code:

class A
{
};

class B : public A
{
    public:
        virtual void f() {}
};

int main()
{
    A* a = new A();
    B* b = static_cast<B*>(a);
    b->f();
}

This program fails with a segmentation fault. There are two solutions to make this program work:

  1. declare f non-virtual
  2. do not call b->f() (i.e. it fails not because of the cast)

However, both are not an option. I assume that this does not work because of a lookup in the vtable.

(In the real program, A does also have virtual functions. Also, the virtual function is not called in the constructor.)

Is there a way to make this program work?

+11  A: 

You can't do that because the object you create is A, not B. Your cast is invalid-- an object of A (created with new) cannot magically become an object of B.

Did you mean the A* a = new A() to actually be A* a = new B()? In that case, I would expect it to work.

Joe
Thank you (all) for your reply.I am wondering about this: Why does this program work if I omit the virtual keyword? (The rest of the code remains unchanged.)I tried to put some functionality in f() (a simple cout) and it gets executed. If it is not an object of type B, this should not be possible. Right?
Alexander
This could be its own question, but the answer is that a (non-virtual) member function call `b->f()` is internally identical to `B::f(b)`. In other words, there is a single function called `B::f` which takes an implicit first argument which is a pointer to the object it is called on (that argument is the `this` pointer, and is used to access the members of the object in question). With virtual functions, it isn't known ahead of time whether to call `B::f` or maybe `C::f` and so a lookup table is required, which uses the `b` pointer and is where your segfault comes from.
Tyler McHenry
(continued, due to space constraints) So with a non-virtual function, the `b->f()` (aka `B::f(b)`) call doesn't actually ever *use* the `b` pointer, unless the `B::f`function tries to access data contained in the `b` object. If the `B::f` body is simply a `cout` statement, there will be no apparent problem. If you for example define an integer member inside of the `B` class and try to print that out in `B::f`, you will likely get a segfault, or a bizarre unexpected value for that integer.
Tyler McHenry
Actually, since class A doesn't have the function f(), trying to call B::f() through a casted pointer where the pointer does not point to a B object results in UB. So the answer to the question "Why does this program work if I omit the virtual keyword?" is "Pure chance."
John Dibling
Thank you for your very interesting and detailed answer.I added a public member (int i) to B and set it in main (b->i = 3). Then I access it in f afterwards (std::cout << i << std::endl;). It gets printed out correctly. Very strange ...However, I believe I have to change my design. This unexpected behavior will likely result in future errors that will be hard to debug ...
Alexander
Yes it might work correctly because all that `b->i = 3` does is store 3 at a particular offset from the memory location pointed to by `b`, and then you print that same location later. This will sometimes work, if you were lucky enough not to clobber any important data with that errant write, and you were lucky enough that whatever really owned that memory didn't overwrite your 3 in the meantime.
Tyler McHenry
+2  A: 

In order to do a static_cast, you should be sure that the object can be casted, i.e. is an object of class B.

In this case, I'm sure it isn't.

xtofl
+3  A: 

You can't do that.

In your example, a is a object of class A. Not B. Casting it to B does not make it a B.

If you want to use polymorphic object behaviors, then you can give virtual function f to class A, and you can use code like A* a = new B(); Then you can use the virtual functions through the a pointer to get behavior from class B.

Zan Lynx
+3  A: 

In your code:

    A* a = new A();

You instantiate an A object. Then you try to use static_cast to go from a base type to a derived type:

B* b = static_cast<B*>(a);

If the value in a pointed to an object that actually was of type B, this would be legal and well-formed. But a does not point to an object of type B, it points to an A, so the cast evokes undefined behavior.

The fix is to change how you instantiate the object. Change:

A* a = new A();

...to:

A* a = new B();
John Dibling