4

I am very new to CPP, learning about late binding polymorphism.

As per what I have read and understood, virtual keyword is used for late binding. which internally at compilation time create a vtable pointed by vptr. So, for example

class BASE{
public:
virtual void f1(){cout<<"BASE F1\n";}
virtual void f2(){cout<<"BASE F2\n";}
void f3(){cout <<"BASE F3\n";}
};

class D1:public BASE{
public: 
    virtual void f1(){cout<<"D1 F1\n";}
    void f2(){cout<<"D1 F2\n";}  
};

class DD1:public D1{
public:
    void f1(){cout<<"DD1 F1\n";}
    void f2(){cout <<"DD1 F2\n";}
};

Here, BASE will be having 2 function in base class vtable:

BASE::f1() 
BASE::f1()

D1 which inherits from BASE, will have inherited vtable:

D1::f1()
BASE::f1

DD1 which inherits from D1, will not have any vtable of its own.

when we create an object:

//case 1:
BASE *b = new D1(); 
b->f1();//will print "D1 F1"
b->BASE::f1();//will print "BASE F1"
b->f2();//will print "D1 F2"
//case 2:
BASE *b1 = new DD1();
b1->f1();//will print "DD1 F1"
b1->D1::f1();//will print "D1 F1"
b1->BASE::f1();//will print"BASE F1"

But, at case: b1->D1::f1(); its giving an compilation error

 error: ‘D1’ is not a base of ‘BASE’

Question: why? should not it print D1 F1 as its virtual function.

After going throw fdump, I found one more interesting thing, which is little confusing;

Vtable for BASE
BASE::_ZTV4BASE: 4u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI4BASE)
16    (int (*)(...))BASE::f1
24    (int (*)(...))BASE::f2

Class BASE
   size=8 align=8
   base size=8 base align=8
BASE (0x7fbc3d2ff120) 0 nearly-empty
    vptr=((& BASE::_ZTV4BASE) + 16u)

Vtable for D1
D1::_ZTV2D1: 4u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI2D1)
16    (int (*)(...))D1::f1
24    (int (*)(...))D1::f2

Class D1
   size=8 align=8
   base size=8 base align=8
D1 (0x7fbc3d31f2d8) 0 nearly-empty
    vptr=((& D1::_ZTV2D1) + 16u)
  BASE (0x7fbc3d2ff180) 0 nearly-empty
      primary-for D1 (0x7fbc3d31f2d8)

Vtable for DD1
DD1::_ZTV3DD1: 4u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI3DD1)
16    (int (*)(...))DD1::f1
24    (int (*)(...))DD1::f2

Class DD1
   size=8 align=8
   base size=8 base align=8
DD1 (0x7fbc3d31f3a8) 0 nearly-empty
    vptr=((& DD1::_ZTV3DD1) + 16u)
  D1 (0x7fbc3d31f410) 0 nearly-empty
      primary-for DD1 (0x7fbc3d31f3a8)
    BASE (0x7fbc3d2ff1e0) 0 nearly-empty
        primary-for D1 (0x7fbc3d31f410)

Question: will not Virtual table of Class BASE get inherited by Class D1, Class D1 vTable & Class BASE get inherited by DD1.? How will inheritance of virtual table take place?

Ashutosh
  • 169
  • 7
  • If a member function is declared virtual in a base class, it is always virtual, and the keyword is optional in derived classes. Thus, your `f2` in `D1` is just as overridden as `f1`, as are both functions in `DD1`. – molbdnilo Oct 05 '13 at 16:21

5 Answers5

4

DD1 which inherits from D1, will not have any vtable of its own.

No, wrong. It will have its own vtable since it overrides virtual functions (the virtual keyword is implied here, since once a function is declared virtual in a base class, it’s virtual everywhere).

Question: why? should not it print D1 F1 as its virtual function.

The static type of b1 is Base*, even if its dynamic type is DD1*. Thus you cannot call b1->D1::f1 because that syntax instructs the compiler to statically resolve the call to a function that (again, statically) is not available in b1. If you absolutely want to execute this call, and know that b1’s dynamic type is in fact D1 (or derived from it), you can cast it to change the static type of the object:

static_cast<D1*>(b1)->D1::f1();
Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
2

I will suggest you to pick up some good book(I suggest Thinking in C++, freely available) and go through the chapter on Virtual Functions to get clarity on this confusing topic.

That said, there are few things you have got wrong.

Quote: D1 which inherits from BASE, will have inherited vtable:

D1::f1()

BASE::f1

Actually what happens is that the vtable contents are replaced in case of derived class if it chooses to override the base class virtual functions. In your case you have done that in D1. So the vtable of D1 will have the D1 functions (yes both of them, f1() and f2() ).

So VTable of D1 is:

D1::f1()
D1::f2()

Base class functions disappear/get overwritten in the D1 vTable.

DD1 vtable has the functions of DD1 in them, instead of the D1.

Regarding the error you see, the answer to that is already posted.

NotAgain
  • 1,927
  • 3
  • 26
  • 42
2

At least a few of your expectations are wrong:

//case 2:
BASE *b1 = new DD1();
b1->f1();//will print "DD1 F1"

Conceivably correct. Maybe it should produce "D1 F1", language lawyers, please help.

b1->D1::f1();//will print "D1 F1"

Why should it do that? b1 is of type BASE *, the compiler has no reason to beleive that the object is in fact a D1, if you want to assert that you need an explicit cast.

b1->BASE::f1();//will print"BASE F1"

Yes.

Adrian Ratnapala
  • 5,485
  • 2
  • 29
  • 39
  • 1
    Language lawyer here: it must produce `"DD1 F1"`. `DD1::f1` is `virtual`. – Konrad Rudolph Oct 05 '13 at 16:17
  • So `DD1::f1` is virtual even though it is not declared us such? If I then overrode created yet another derived class `D31`, and tried to call that method via a pointer to a `DD1`, which method would be called? – Adrian Ratnapala Oct 05 '13 at 16:19
  • 2
    Once virtual, always virtual. It *is* declared virtual – in the base class. Overriding a function does not redeclare it, just replace its implementation. It’s maybe unfortunate that C++ allows omitting the `virtual` keyword in overridden functions, but the effect is the same as if it were written. – Konrad Rudolph Oct 05 '13 at 16:21
  • Ok, I believe it. And I do think it is unfortunate. – Adrian Ratnapala Oct 05 '13 at 16:22
  • I think this "once virtual, always virtual" is an important and non-obvious thing. That's why I edited your comment below (or above) to make it explicit. Maybe you should edit it in your own words. – Adrian Ratnapala Oct 05 '13 at 16:39
  • Thanks for the edit. I actually accidentally rejected it (sorry!), and then edited it in myself. – Konrad Rudolph Oct 05 '13 at 16:46
1

"Why should not it print D1 F1 as its virtual function?"

Because the pointer b1 is of type BASE. You're effectively trying to access the D1 class from a BASE object, which the compiler will not allow. What you need to do is inform the compiler that the b1 pointer is a valid D1 object, like so:

dynamic_cast<D1*> (b1) -> f1()
Gearoid Murphy
  • 11,834
  • 17
  • 68
  • 86
  • 1
    No need for `dynamic_cast` unless you actually check whether the cast succeeded. – Konrad Rudolph Oct 05 '13 at 16:16
  • 1
    Good point. http://stackoverflow.com/questions/4648070/static-cast-vs-dynamic-cast – Gearoid Murphy Oct 05 '13 at 16:18
  • 1
    Quote "static_cast performs no runtime checks. This should be used if you know that you refer to an object of a specific type, and thus a check would be unnecessary" Don't use static_case for downcasting, even it's trivial in this example, it will cause you troubles in real-life application. – Iuri Covalisin Oct 05 '13 at 16:31
  • 1
    This example will invoke DD1 and not D1, please see my answer for correct invocation (FQN). – Iuri Covalisin Oct 05 '13 at 16:35
  • 3
    @IuriCovalisin You fundamentally misunderstand how `dynamic_cast` and `static_cast` work, apparently. See the explanation linked in the comment above. – Konrad Rudolph Oct 05 '13 at 16:38
  • @KonradRudolph Rudolph That's great you focus on static vs dynamic, while missing the error in this answer. As I wrote before - exactly in this example we can be sure about the type. But whenever you write real-life code, you can never be sure with downcasting, this code can be moved to a method accepting a pointer or pointer few lines above can initialize another Base inherited class, and this casting error will not be trivial to discover with static cast and will be checked runtime with dynamic cast. – Iuri Covalisin Oct 05 '13 at 17:03
  • Quote from MSDN "In general you use static_cast when you want to convert numeric data types such as enums to ints or ints to floats...", "A dynamic_cast to an ambiguous pointer will fail, while a static_cast returns as if nothing were wrong; this can be dangerous" http://msdn.microsoft.com/en-us/library/c36yw7x9.aspx – Iuri Covalisin Oct 05 '13 at 17:04
  • 1
    @Iuri I wasn’t addressing the part where this answer is wrong at all, I was specifically addressing the rest of your comment. And your assertion that “whenever you write real-life code, you can never be sure with downcasting” is simply wrong. There are many such situations, and in fact in my experience they predominate. Using `dynamic_cast` in such situations is [cargo cult programming](https://en.wikipedia.org/wiki/Cargo_cult_programming), just like checking for `nullptr` after calling `new` (`new` never returns `nullptr` unless called with `std::nothrow`). – Konrad Rudolph Oct 05 '13 at 17:07
0

You cannot do fully qualified name (FQN) method call from the Base class, which has no clue about class D1.

Here's the solution - downcast to DD1 or D1 and then do FQN call:

(dynamic_cast<DD1*>(b1))->D1::f1();//will print "D1 F1"
Iuri Covalisin
  • 645
  • 4
  • 7
  • 1
    Same comment as for Gearoid’s answer: `dynamic_cast` only makes sense if you actually check whether the cast succeeds (and if there’s a chance of it not succeeding). Otherwise use `static_cast`. – Konrad Rudolph Oct 05 '13 at 16:22
  • Static cast cannot identify problem as well - this example still compiles and runs BASE *b1 = new D1(); b1->f1();//will print "DD1 F1" (static_cast(b1))->D1::f1();//will print "D1 F1" – Iuri Covalisin Oct 05 '13 at 16:27
  • I don’t understand the comment. `static_cast` works exactly as `dynamic_cast` here, except that the latter implies a different usage (its meaning is “I’m not sure this cast will succeed, therefore I need to check its return value). – Konrad Rudolph Oct 05 '13 at 16:29
  • use static_cast only when you are sure about the types, and you can never be sure about the types while downcasting. – Iuri Covalisin Oct 05 '13 at 16:33
  • 1
    That’s completely wrong. In OP’s example we *are* sure about the type. In fact, we are sure about the type in many (I’d say *most*) downcasting scenarios. And in any case, your code uses `dynamic_cast` wrong since it doesn’t check whether the return value is `nullptr`. – Konrad Rudolph Oct 05 '13 at 16:36
  • Konrad is right. There is no point doing a dynamic cast if you don't check for success. If you can prove that it will succeed, then use static_cast. This looks like an example of people using "best practice" instead of actually trying to handle errors. – Adrian Ratnapala Oct 05 '13 at 16:45
  • Using static because you can prove it here in this small academic example looks like academic approach vs based on practice. You are correct if you are a student or wrong if write real applications. – Iuri Covalisin Oct 05 '13 at 17:36