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

1.5k Views Asked by At

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?

3

There are 3 best solutions below

2
On BEST ANSWER

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"; }
};
5
On

Functions with the same name but different signatures are essentially different functions.

By declaring virtual void describe(int dummy) in your Dragon class, you have declared a new virtual function, not overriding the original one (virtual void describe() in Enemy). You can only override virtual functions with the same signature.

You cannot call describe(1) on a pointer to Enemy because c++ calls function according to the instance's compile time type (although such a call can be dynamically dispatched to call the actual overrode method).

0
On

In C++, functions that have the same name but different parameters are completely independent functions, that have nothing to do with each other. The fact that they have the same name is completely immaterial.

It is exactly the same as if you called the function in the base class "apple", and the one in the derived class "banana". Since there is no "banana" function in the base class, you cannot obviously call it in the base class. The banana function in the derived class obviously does not override the function in the base class.

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.

That is incorrect. It hides it only if it has the same name, but also identical parameters (and any qualifiers, if there are or are not any).