I try to understand the role of the virtual keyword when changing the signature of a member function in a derived class compared to a base class (I am aware that this is bad practice, but I still want to understand).
Consider the following example from a book. There are two lines that the book says lead to an error. I added my understanding and questions in the comments.
class base_class {
public:
virtualvoid vf1();
virtualvoid vf2();
virtualvoid vf3();
};
class derived_class : public base_class {
public:
virtualvoid vf1();
virtualvoid vf2(int); // the change in parameter list seems to be allowed.
virtualchar vf3(); // ERROR. why is this change in signature not allowed when changing the parameter list is allowed?
};
int main () {
derived_class d;
base_class *bp = &d;
bp->vf1(); // derived_class::vf1() since virtual
bp->vf2(); // base_class::vf2() as derived_class does not have function with this signature
d.vf2(); // not possible for same reason
// due to re-using the name vf2 in derived_class for a function
// with another signature than in base_class, the function base_class::vf2() was not inherited.
d.vf2(7); // works
bp->vf2(7); // ERROR because base_class does not have corresponding
// function. But I thought the role of the "virtual" keyword
// is to ensure that the one function is executed that belongs
// to the object that the pointer points to. So it should be enough
// if vf2(int) exists in the derived_class...
}
Someone else can probably better explain how overloads don't cross class boundaries unless you specifically allow each one.
But I just want to say: This is one reason to use the override specifier when possible. (If your book was pre-C++11, it might not have been available.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
class base_class {
public:
virtualvoid vf1();
virtualvoid vf2();
};
class derived_class : public base_class {
public:
virtualvoid vf1();
//virtual void vf2(int); // uncomment --> still compiles
virtualvoid vf2(int) override; // uncomment --> fails to compile, because you aren't actually overriding anything!
};
int main() { }
error: 'vf2' marked 'override' but does not override any member functions
> the change in parameter list seems to be allowed.
> the function base_class::vf2() was not inherited.
It is inherited, but the base class function is hidden ie. it is not found by unqualified lookup.
A function with the same name but different parameter list does not override the base function of the same name, but hides it: when unqualified name lookup examines the scope of the derived class, the lookup finds the declaration and does not examine the base class. https://en.cppreference.com/w/cpp/language/virtual#In_detail
> why is this change in signature not allowed?
Arbitrary changes in the return type is not allowed; the type of the expression Derived::f() must be known at compile time, even if the call is dispatched at run time.
Essentially for the same reason that ( x==y ? 1234 : "abcd" ) is not allowed.
If the function Derived::f overrides a function Base::f, their return types must either be the same or be covariant.
...
When a virtual function call is made, the type returned by the final overrider is implicitly converted to the return type of the overridden function https://en.cppreference.com/w/cpp/language/virtual#Covariant_return_types
bp->vf2(7); // ERROR because base_class does not have corresponding
// function. But I thought the role of the "virtual" keyword
// is to ensure that the one function is executed that belongs
// to the object that the pointer points to. So it should be enough
// if vf2(int) exists in the derived_class...
After having
1 2 3 4 5
class derived_class : public base_class {
public:
virtualvoid vf2(int); // the change in parameter list seems to be allowed.
}
I would assume that the compiler knows that it simply needs to look up the list of virtual functions associated to derived_class during run time.
Why would it be necessary that the function also exists in the parent class?
Another question:
Is it true that virtual function calls lead to slowed runtime?
I.e. if efficiency is an issue and I wanted to call derived_class::vf1(), instead of doing
1 2 3 4 5 6
int main () {
derived_class d;
base_class *bp = &d;
bp->vf1(); // virtual function call
I should rather do
1 2 3 4 5 6
// continue main
d.vf1(); // call by name
derived_class *dp = &d;
dp->vf1(); // call via pointer of type derived_class*
bp is a base-class pointer: it can only be used to point to member functions that are declared in the base class (even if their definition has to wait until the derived class).
There is no vf2(int) function in the base class. The only similarly-named function there takes no arguments.
Anything which has to be decided at runtime will, in principle, be a tiny bit slower: but I doubt that you would be able to detect the difference, and it would be far outweighed by the benefits of polymorphism.
Is it true that virtual function calls lead to slowed runtime?
If class has a virtual function, then it does have a lookup table.
When you call virtual function (be it via bp or dp), the pointer to implementation is looked up from that table (during runtime).
If you want the call to be tiny bit faster, then the function cannot be virtual.
> Is it true that virtual function calls lead to slowed runtime?
The optimiser may not be able to inline the virtual call.
For a (typically small) function which is called a large number of times, this (the inability to optimise across the function call) may make for a significant difference in performance.
For example:
a.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
struct A
{
virtualint foo() = 0 ;
int bar() { return 0 ; }
};
struct B : A
{
virtualint foo() override { return ++x ; }
int bar() { return ++x ; }
int x = 0 ;
};
B b ;
A& a = b ;
Your example only has a single derived object, so it's difficult to find a reason not to do a direct call using that interface, rather than using dynamic polymorphism and call via the base class.
Real world code that uses dynamic polymorphism isn't like this. Often the actual concrete classes that are used, by library code, isn't known at the time of writing. There is no concrete derived class that you can use that you can call directly.
For example, ostream and istream have been base clases in the C++ standard library from the start. Derived classes based on these have appeared in many popular libraries; from Tools.h++ to Boost and beyond.
You're not comparing like with like, that's why you appear to have the luxury of a choice. In the real world, you don't.