-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
42: Practice Exercise: Building a Class-Based Shape Hierarchy
I've spent a lot of time reviewing junior code, and there is one pattern that pops up almost every single time someone first tackles a class hierarchy: the attempt to store derived objects in a container of base objects by value. It usually looks something like this:
std::vector<Shape> shapes;
shapes.push_back(Circle(5.0)); // Wait, where did my radius go?
The Myth of the Magic Vector
The misconception here is that C++ is "smart" enough to know that a Circle is a Shape and will somehow preserve the Circle's identity inside that vector. It won't. What's actually happening is called object slicing.
When you push a Circle into a vector<Shape>, the compiler allocates exactly enough space for a Shape object. It then copies only the Shape part of your Circle into that slot, effectively "slicing off" everything that makes it a circle—the radius, the specific area() override, all of it. Even if your area() function is marked virtual, it doesn't matter because the object in the vector is no longer a Circle; it's just a Shape.
To get actual polymorphism, you have to store pointers or references. I almost always recommend std::unique_ptr<Shape> these days. It tells the compiler: "I don't know exactly which shape this is, but it's located somewhere in memory, and I want to interact with it through the Shape interface."
The Virtual Destructor Safety Net
Once you move to pointers, you'll hit a second common trap. If you have a base class Shape and a derived class Circle, and you delete a Circle object via a Shape* pointer, C++ will only call the Shape destructor unless you explicitly tell it not to.
If your Circle class allocated memory for a label or a texture, that memory is now leaked. It's a silent killer. Always, without exception, make your base class destructor virtual:
class Shape {
public:
virtual ~Shape() = default; // This ensures derived destructors are called
virtual double area() const = 0;
};
Designing for Extensibility
When building your hierarchy, aim for the "Open-Closed Principle." Your Shape base class should be open for extension (you can add Triangle, Pentagon, etc.) but closed for modification (you shouldn't have to change the Shape class every time you add a new shape).
This is achieved by using pure virtual functions (the = 0 syntax). By doing this, you're creating a contract. You're saying: "I don't know how to calculate the area of a generic shape, but I guarantee that any class claiming to be a Shape will know how to do it." This forces derived classes to implement the logic, or they simply won't compile.
📋 Practical Task
Implementation Project: The Polymorphic Area Calculator
Your task is to build a small system that can calculate the total area of a collection of different geometric shapes without the main logic knowing which specific shapes are being used.
Requirements:
- Create an abstract base class called
Shapewith a pure virtual functiondouble area() constand a virtual destructor. - Implement three derived classes:
Circle(requires radius),Rectangle(requires width and height), andTriangle(requires base and height). - In your
main()function, create astd::vectorofstd::unique_ptr<Shape>. - Populate the vector with at least one of each shape type using
std::make_unique. - Iterate through the vector using a loop and calculate the sum of all areas.
- Print the total area to the console.
Constraints: Do not use a switch statement or if/else blocks to check the type of the shape (no dynamic_cast). Rely entirely on the virtual function call to handle the calculation.
There are no comments for now.