0

Here's a piece of code I had written to see the behaviour during downcasting.

#include <iostream>
using namespace std;

class base {
public :
    void function()
    {
        cout << "\nInside class Base";
    }
};

class derived : public base {
public :
    void function()
    {
        cout << "\nInside class Derived.";
    }
};

int main()
{
    base * b1 = new base();
    base * b2 = new derived();
    derived * b3 = (derived*)b1 ;
    b1 -> function();
    b2 -> function();
    b3 -> function(); // print statement 3
    static_cast<derived*>(b2) -> function();
    static_cast<derived*>(b1) -> function(); // print statement 5
    return 0;
}

The output is as follows .

Inside class Base
Inside class Base
Inside class Derived.
Inside class Derived.
Inside class Derived.

I feel print statement 3 and print statement 5 should have displayed "Inside class base" .

Can someone please explain what I might be missing here?

Sumit Das
  • 1,007
  • 9
  • 17
  • You want `virtual` functions. Normally, the function is chosen based on the static (compile time) type it is being called on (i.e. you call it on a `derived*`, you get the `derived` version). If you make the function `virtual`, you get the version for the "actual" (or *dynamic*) type, as you want. e.g. see [here](http://en.wikipedia.org/wiki/Virtual_function) or [here](http://www.learncpp.com/cpp-tutorial/122-virtual-functions/). – BoBTFish Jul 26 '13 at 13:01

4 Answers4

5

Both are cases of undefined behavior. Casting b1 to derived* is not valid.

However, if you said base* b1 = new derived(), you would have the same behavior. Since neither function is marked virtual, then it only checks objects type at compile time.

So the first case would print "Inside class Base", even though its actually a derived pointer.

Dave S
  • 20,507
  • 3
  • 48
  • 68
  • 2
    It looks like both 3 and 5 are UB. They are almost identical. – n. m. could be an AI Jul 26 '13 at 13:03
  • Not sure about this, but I have an inkling it is ok because they are standard layout classes (empty in fact, although that doesn't mean `sizeof(base)==0` or for `derived`). I.e. you are allowed nasty casts in some cases to be able to do what everyone does in c anyway. – BoBTFish Jul 26 '13 at 13:08
  • @BoBTFish: While it works in this case, mostly for the reasons you state, it doesn't have to: 5.2.9p11 _... If the prvalue of type “pointer to cv1 B” points to a B that is actually a subobject of an object of type D, the resulting pointer points to the enclosing object of type D. Otherwise, the result of the cast is undefined._ – Dave S Jul 26 '13 at 13:19
  • Took me a while to dig through The Standard for the right terminology, but I shall just direct you to [this](http://stackoverflow.com/a/7762964/1171191), and mention the phrase "*layout-compatible types*". (Although there it mentions `reinterpret_cast`, rather than `static_cast`.) – BoBTFish Jul 26 '13 at 13:45
  • @BoBTFish: Ah, I wasn't aware they said that `reinterpret_cast` of two layout-compatible types was valid. So it's not necessarily as ill-formed as at first glance. – Dave S Jul 26 '13 at 13:49
  • @DaveS Nope. Pretty tricky though, I was very hesitant to say it. And there's no way I would write it in production code or allow it through code review, well defined or not. – BoBTFish Jul 26 '13 at 13:52
1

You need to define base method void function() as virtual:

virtual void function()
{
    cout << "\nInside class Base";
}

and resulting output is:

Inside class Base
Inside class Derived.
Inside class Base
Inside class Derived.
Inside class Base

In OP, 5th case may not be an undefined behaviour as stated in the reference1 and inline member function memory is not stored like data members as stated here insuring that after static cast to derived type, derived member function is called:

The inverse of any standard conversion sequence (Clause 4) not containing an lvalue-to-rvalue (4.1), array-to- pointer (4.2), function-to-pointer (4.3), null pointer (4.10), null member pointer (4.11), or boolean (4.12) conversion, can be performed explicitly using static_cast.

1 Working Draft, Standard for Programming Language C++, 5.2.9 Static Cast - 7

Community
  • 1
  • 1
fatihk
  • 7,789
  • 1
  • 26
  • 48
  • I understand that if you declare it as virtual , then the above output can be seen . But my doubt is that the derived class function should not be there in the memory layout of b1 at all . So how can a function be called which is not present in the memory? – Sumit Das Jul 26 '13 at 13:42
0

The functions are dispatched at compilation time, based solely on the static type, as they are not virtual.

Static type of b3 in print statement is Derived *, hence 'Inside class Derived'. Print statement 5 casts Base * to Derived *, hence the same printout.

Add virtual to function() definition in Base and check again to see what happens.

Tomek
  • 4,554
  • 1
  • 19
  • 19
  • But b1 should contain only the function which displays "Inside class base" since it points to a object of type base . The derived class function should not be there at all in the memory layout of b1 . So how can it be possibly pointed to? – Sumit Das Jul 26 '13 at 13:36
-1

That's the expected behavior.
Non virtual methods are called by the object's type at compile time.
You used a static cast, so the compiler treats it as "Derived" class.

If you'd declare the method as virtual then it will create a virtual look up table for the function, and call the method according to the actual run-time type.

Yochai Timmer
  • 48,127
  • 24
  • 147
  • 185
  • Personally, I expected it to print a purple unicorn, or whatever else it feels like: expected undefined behavior is rathrr misleading, even if what you describe is in line with many implementations. – Yakk - Adam Nevraumont Jul 26 '13 at 13:09
  • @Yakk Only line 3 is not well defined. But what it really does is that the compiler will treat the object as the object you said it is (just like reinterpert_cast). Because the objects are related the compiler will add the right offset to get to the base of the derived class. Then it will call the derived class's function. Because there are no members to either class that offset will be 0. – Yochai Timmer Jul 26 '13 at 13:14
  • @Yakk just because it's not well defined in the C++ standard doesn't mean it's not an expected behavior if you know how the compiler works. – Yochai Timmer Jul 26 '13 at 13:15