Here's an example I wish I'd seen earlier when learning about inheritance in C++.
Let's start with a simple base class.
class Base {
public:
    void method() {
        speak("base method\n");
    }
};
Here speak is just a wrapper around cout and endl.
void speak(const string out) {
    cout << out << endl;
}
Let's now inherit from Base and re-use the same method name.
class Derived : public Base {
public:
    void method() {
        speak("derived method\n");
    }
};
So: base classes print "base", derived classes print "derived". However, what happens if we mix types? Concretely, what would the output of the following program be?
#include <iostream>
using std::cout;
using std::endl;
using std::string;
// convenience function to print
void speak(const string out) {
    cout << out << endl;
}
class Base {
public:
    void method() {
        speak("base method\n");
    }
};
class Derived : public Base {
public:
    void method() {
        speak("derived method\n");
    }
};
int main() {
    speak("ex1: base class");
    Base base = Base();
    base.method();
    speak("ex2: derived class");
    Derived derived = Derived();
    derived.method();
    speak("ex3: base class ref");
    Base& derived_ref = derived;
    derived_ref.method();
    // Derived& base_ref_error = base;
    // error: non-const lvalue reference to type 'Derived' cannot bind to a 
    // value of unrelated type 'Base'
}
Examples 1 and 2 are straightforward. What about example 3? Intuitively, we expect derived_ref.method() to print out "derived". It is a reference to a Derived object. However, this is the output:
ex1: base class
base method
ex2: derived class
derived method
ex3: base class ref
base method
Shock! The reason is that, in example 3, derived_ref is of type Base&. The name is misleading: it's really a base_ref :). When it looks for method it looks it up in the Base class.
When dealing with inheritance this situation is more common than one would like, especially when dealing with abstract classes / interfaces.
As an aside: the last example Derived& base_ref_error = base; would not compile if uncommented.
runtime polymorphism
So, how do we fix example 3 above? The solution is to mark the base class method as virtual.
#include <iostream>
using std::cout;
using std::endl;
using std::string;
// convenience function to print
void speak(const string out) {
    cout << out << endl;
}
class Base {
public:
    virtual void method() {
        speak("base method\n");
    }
};
class Derived : public Base {
public:
    void method() override {
        speak("derived method\n");
    }
};
int main() {
    speak("ex1: base class");
    Base base = Base();
    base.method();
    speak("ex2: derived class");
    Derived derived = Derived();
    derived.method();
    speak("ex3: base class ref");
    Base& derived_ref = derived;
    derived_ref.method();
}
This time the output is:
ex1: base class
base method
ex2: derived class
derived method
ex3: base class ref
derived method
Great! This makes more sense. The variable derived_ref is, after all, built from derived, which is of type Derived.
Note: the override keyword in the Derived class is unnecessary. However, it's useful to have it there to help the compiler find bugs.
abstract classes
A typical scenario where the virtual stuff comes up is with abstract classes. Here is an example:
class AbstractBase {
public:
    virtual void method() = 0;
    virtual ~AbstractBase() = default;
};
We have modified base to have method be unimplemented. The =0 means we require any derived class to implement that method.
We also mark the destructor as virtual, as subtle bugs can arise if we don't. See below.
#include <iostream>
using std::cout;
using std::endl;
using std::string;
// convenience function to print
void speak(const string out) {
    cout << out << endl;
}
class AbstractBase {
public:
    virtual void method() = 0;
    virtual ~AbstractBase() = default;
};
class Derived : public AbstractBase {
public:
    void method() override {
        speak("derived method\n");
    }
};
int main() {
    // AbstractBase abstractbase = AbstractBase();
    // error: allocating an object of abstract class type 'AbstractBase''Base'
    speak("ex2: derived class");
    Derived derived = Derived();
    derived.method();
    speak("ex3: base class ref");
    AbstractBase& derived_ref = derived;
    derived_ref.method();
}
The output is
ex2: derived class
derived method
ex3: base class ref
derived method
as expected.
As an aside: the first example AbstractBase abstractbase = AbstractBase(); does not compile, as AbstractBase is, indeed, abstract.
destructors
Let's clarify what happens if we don't have a destructor marked as virtual. Here is a first example.
#include <iostream>
using std::cout;
using std::endl;
using std::string;
// convenience function to print
void speak(const string out) {
    cout << out << endl;
}
class Base {
public:
    ~Base() {
        speak("base destruct");
    }
};
class Derived : public Base {
public:
    ~Derived() {
        speak("derived destruct");
    }
};
int main() {
    Derived der; 
}
If we run this, the output is
derived destruct
base destruct
as the destructors for der are invoked in order. However, tweaking this slightly, we get some unexpected behavior (I got this example from "C++ Crash Course" by Josh Lospinoso).
#include <iostream>
using std::cout;
using std::endl;
using std::string;
// convenience function to print
void speak(const string out) {
    cout << out << endl;
}
class Base {
public:
    ~Base() {
        speak("base destruct");
    }
};
class Derived : public Base {
public:
    ~Derived() {
        speak("derived destruct");
    }
};
int main() {
    Base* x{new Derived()};
    delete x;
}
The output is
base destruct
so ~Derived is never invoked! This could lead to serious issues. The simple fix is to have the destructor be virtual.
#include <iostream>
using std::cout;
using std::endl;
using std::string;
// convenience function to print
void speak(const string out) {
    cout << out << endl;
}
class Base {
public:
    virtual ~Base() {
        speak("base destruct");
    }
};
class Derived : public Base {
public:
    ~Derived() {
        speak("derived destruct");
    }
};
int main() {
    Base* x{new Derived()};
    delete x;
}
so now the output is
derived destruct
base destruct
as expected.
 

 
    
Top comments (0)