Search code examples
c++c++11virtual-functionsdynamic-bindingmethod-hiding

Base-class pointer pointing to a derived class cannot access derived-class method


I am learning inheritance in C++11, and I found that if a derived class has redefined a virtual function name but with a different prototype, a base-class pointer assigned with a pointer to the derived class can only access the base class version of the function. The derived version function cannot be accessed. I wonder why this happens.

class Enemy {
public:
  virtual void describe() { std::cout << "Enemy"; }
};
class Dragon : public Enemy {
public:
  virtual void describe(int dummy) { std::cout << "Dragon"; }
};

In main,

Dragon foo;
Enemy* pe = &foo;
pe->describe(); // Enemy
foo.describe(1); // Dragon
pe->describe(1); // no matching function, candidate is Enemy::describe()

From what I know about virtual function tables, the derived object that pe points to (i.e. foo) should have a vpointer member that points to Dragon's vtable. I also know that redefinition of a function name in the derived class will hide all the functions of the same name in the base class. So in Dragon's vtable the address of 'describe' should be the function with parameter int dummy.

But it turns out that pe can access Enemy's version of the method, which is supposed to be hided. And pe cannot access Dragon's version of the method, which is supposed to be in pe's vtable. It performs as if the Enemy's vtable is used. Why this happens?

Update: I think now I more or less understand the mechanisms behind it. Here is my hypothesis:

Since it is a pointer to Enemy, the program will first find the method name in Enemy's scope. If the name is not found, the compiler gives an error. If it is not virtual, then call it. If it is virtual, then record the method's offset in Enemy's vtable. Then the program use this offset to access the right method in the target object's vtable.

If the method is properly overrided, the function address in target object's vtable at that offset would have been changed. Otherwise, it will be the same function address as in the Enemy's vtable, as in the example.

Since Dragon's describe with int dummy is a different prototype, it is added to the Dragon's vtable after the original describe it inherited from Enemy. But the int dummy version cannot be accessed from Enemy* because Enemy's vtable doesn't even have that offset.

Is this correct?


Solution

  • In fact you have:

    class Enemy {
    public:
      virtual void describe() { std::cout << "Enemy"; }
    };
    
    class Dragon : public Enemy {
    public:
      // void describe() override { Enemy::describe(); } // Hidden
      virtual void describe(int dummy) { std::cout << "Dragon"; }
    };
    

    Selection of overload method is done statically:

    • pointers/references on Enemy only see void Enemy::describe()

    • pointers/references on Dragon only see void Dragon::describe(int) (but could explicitly have access to void Enemy::describe()).

    Then virtual dispatch is done with runtime type.

    So

    Dragon foo;
    Enemy* pe = &foo;
    
    foo.describe();         // KO: Enemy::describe() not visible (1)
    foo.Enemy::describe();  // OK: Enemy::describe()
    foo.describe(1);        // OK: Dragon::describe(int)
    
    pe->describe();         // OK: Enemy::describe()
    pe->describe(1);        // KO: No Enemy::describe(int)
    pe->Dragon::describe(1);// KO: Dragon is not a base class of Enemy
    

    (1) can be fixed by changing Dragon with

    class Dragon : public Enemy {
    public:
      using Enemy::describe; // Unhide Enemy::describe()
    
      virtual void describe(int dummy) { std::cout << "Dragon"; }
    };