In the previous section, we covered the fundamental concepts of inheritance in C++. In this session, we will expand further on more C++ inheritance features that enable useful code structures for working with complex data types.
§1 Upcasting and Downcasting #
Upcasting converts a derived reference or pointer to its base type implicitly. Downcasting converts a base class reference to a derived type. These conversions are useful for storing and working with different object types within the same list or data structures.
Assume we have a base class Animal and a pair of derived classes: Dog and Duck:

Upcasting: (moving “up” the inheritance tree) occurs when you cast a derived class to a base class. This is safe and automatic.
Dog myDog;
Animal* animalPtr = &myDog; // Upcasting a Dog* pointer to Animal*
Downcasting: (moving “down” the inheritance tree) Occurs when converting a base class pointer to a derived class type. This requires an explicit cast and is less safe since it assumes the base class reference is actually pointing to a derived class object.
Types of Downcasting:
- Static Downcasting: Using
static_cast<Dog*>(animalPtr);, which doesn’t perform runtime checks. You use it when you are certain of the object’s type. Static casts are performed at compile time and are therefore faster.
// downcast `animal` pointer to derived class type
Dog* dogPtr = static_cast<Dog*>(animal);
dogPtr->bark();
- Dynamic Downcasting: Using
dynamic_cast<Dog*>(animalPtr);, which checks at runtime if the downcast is valid (only works with polymorphic types, i.e., classes with at least one virtual function). We can verify that a dynamic downcast succeeded at runtime with:
// downcast `animal` pointer to derived class type
if (Dog* dogPtr = dynamic_cast<Dog*>(animal)) {
// able to call derived class methods here
dogPtr->bark();
}
§2 Identifying Class Pointer Types #
you can use dynamic_cast to discover the real derived type behind a base pointer. This is another technique that allows you to store different object types together, and later be able to identify and utilize the objects true type.
class Animal {
public:
void eat() { cout << "eating food." << endl; };
// Make Animal polymorphic by declaring the default destructor `virtual`
// more on this in the next section.
virtual ~Animal() = default;
};
class Duck : public Animal {
public:
void quack() { cout << "Quack."; }
};
class Dog : public Animal {
public:
void bark() { cout << "Bark."; }
};
int main() {
Dog dog;
Animal animal;
Duck duck;
// create an array of Animal pointers, referencing the derived objects
Animal *animalArr[3] = {&dog, &animal, &duck};
// use dynamic casts to identify pointer type and execute code based on type
for (Animal *animal : animalArr) {
if (Dog* dogPtr = dynamic_cast<Dog*>(animal)) {
dogPtr->bark();
dogPtr->eat();
}
else if (Duck *duckPtr = dynamic_cast<Duck *>(animal)) {
duckPtr->quack();
duckPtr->eat();
}
else {
cout << "Not a Dog or Duck.";
animal->eat();
}
}
}
Outputs:
$ ./example
Bark. eating food.
Not a Dog or Duck. eating food.
Quack. eating food.
In this example, we use dynamic_cast to cast a base pointer to its appropriate derived class, this allows us to identify which type a base pointer represents and adapt our program behavior. notice how the base class methods eat() are always available and valid in all cases.
This can be useful when storing lists of different objects, as we can use polymorphism to change behavior based on the object type.
§3 Abstract Base Class (Pure Virtual Functions) #
An abstract base class serves as a blueprint for other classes. It defines a common interface (typically using virtual functions) for derived classes, but it does not provide a complete implementation. This enables polymorphism, allowing you to handle objects of different derived classes uniformly.
For a class to be abstract, it must contain at least one pure virtual function. This is a virtual function that has no implementation in the base class. It’s declared by setting the value of the function equal to zero = 0. When a base class has a pure virtual function, it becomes abstract, and no objects of that class can be instantiated. You must declare a derived class to utilize any methods in an abstract base class.
class Shape {
public:
virtual void draw() = 0; // Pure virtual function, the `Shape` class is now abstract
};
In the example above, Shape is an abstract class because it contains a pure virtual function draw(). Any derived class of Shape must implement the draw() function, or it too will become abstract. Abstract classes cannot be instantiated.
for example:
class Shape {
public:
virtual void draw() = 0; // Pure virtual function
// Regular member function with implementation
void info() const {
cout << "This is a shape." << endl;
}
};
class Circle : public Shape {
public:
void draw() const override { // implements abstract `draw()` function
cout << "Drawing a Circle." << endl;
}
};
int main() {
Circle circle;
Shape* shape = &circle; // abstract class pointers can still reference derived objects
shape->draw(); // automatically calls the `Circle` implementation of draw()
shape->info();
}
$ ./example
Drawing a Circle.
This is a shape.
- The Shape class is abstract because it contains the pure virtual function
draw(). - Despite being abstract, Shape has an implemented
info()function, which derived classes likeCirclecan call. - This structure is useful when you want to provide shared functionality across all derived classes, while still enforcing specific methods (like
draw()) to be implemented uniquely by each derived class.
§4 Direct and Indirect Base Classes #
In C++, when a class inherits from another class, that base class becomes part of the inheritance hierarchy. If this base class itself inherits from another class, we refer to the original as an indirect base class.
Example:
class Animal { // Base class: Animal
public:
void speak() { cout << "Animal Sound\n"; }
};
class Mammal : public Animal { // Derived from Animal
public:
void run() { cout << "Mammal Running\n"; }
};
// Derived from Mammal (indirectly inherits Animal through Mammal)
class Dog : public Mammal {
public:
void bark() { cout << "Woof!\n"; }
};
int main() {
Dog dog;
dog.speak(); // Inherited from Animal
dog.run(); // Inherited from Mammal
dog.bark(); // Defined in Dog
}
$ ./example
Animal Sound
Mammal Running
Woof!
The inheritance structure of this example can be visualized with this UML diagram:
Indirect Inheritance allows Dog to access Animal’s member functions, even though Dog does not directly inherit from Animal.
§5 Further Reading #
- Textbook Section 11.11 Inheritance
- Inheritance Beginners Tutorial: https://www.techgeekbuzz.com/tutorial/c++/cpp-inheritance/
§6 Questions #
- What is upcasting in C++ inheritance and why is it safe?
- When casting a base class pointer to a derived class, is
static_castordynamic_castmore performant?
- What is the main downside of using
static_cast?
- Why is dynamic casting useful when using an array of base class pointers, such as in
Animal* animalArr[3]?
- What causes a class to become an abstract class?
- Can you create an instance of an abstract class?
- Explain why this code does not work, what change is needed to get the
main()function to run as-is?
class Shape {
public:
// Pure virtual function
virtual void draw() const = 0;
// Virtual destructor (important for base classes with virtual functions)
virtual ~Shape() = default;
};
class Circle : public Shape {
private:
int size = 0;
public:
void create(int size) {this->size = size;}
void draw() {}
};
int main() {
Circle* circle = new Circle();
}
- Is an abstract base class allowed to implement methods?
- What is indirect inheritance?
Don’t forget to submit your UML for the associated programming assignment!