-
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
191: Practice Exercise: Building a Shape Hierarchy with Area Calculations
A few years ago, I was reviewing code for a junior dev who was building a simple 2D layout tool. He had created separate classes for Rectangle and Circle, which was fine. The problem started when he needed to calculate the total area of all elements on the canvas. He had one std::vector for rectangles and another for circles. Every time he added a new shape—like a triangle or a pentagon—he had to create a new vector and add another for loop to his summation logic. It was a maintenance nightmare. I told him, "You're treating these as different species, but for the purposes of area, they're all just Shapes."
Defining the Abstract Contract
The trick to solving that mess is to stop caring about the specific type of shape when you're performing general operations. We do this by creating an abstract base class. In C++, this means defining a class with at least one pure virtual function. By declaring virtual double getArea() = 0; in a Shape class, you aren't providing an implementation—because a generic "shape" doesn't have a formula for area. Instead, you're creating a contract. Any class that inherits from Shape must implement getArea(), or the compiler will refuse to let you instantiate it.
class Shape {
public:
virtual ~Shape() = default; // Always use a virtual destructor in hierarchies
virtual double getArea() const = 0;
};
I can't stress the virtual destructor enough. If you delete a Circle object through a Shape* pointer and the destructor isn't virtual, you're looking at undefined behavior and likely memory leaks. It's one of those C++ "gotchas" that will haunt your debugger for hours if you forget it.
Overriding Logic in Derived Classes
Once the contract is set, the derived classes handle the heavy lifting. The Rectangle class only cares about width and height; the Circle only cares about the radius. Because they override the same function signature, they can be treated interchangeably by the rest of your program. This is the essence of polymorphism.
When you implement these, remember to use the override keyword. It doesn't change how the code runs, but it tells the compiler, "I intend to override a virtual function from the base class." If you accidentally mistype the function name or get the const qualifier wrong, the compiler will scream at you immediately rather than silently creating a new, unrelated function.
Managing Heterogeneous Collections
This is where the magic actually happens. Instead of maintaining five different lists for five different shapes, you maintain one: a std::vector of pointers to the base class. Using std::unique_ptr is the professional way to handle this—it ensures that when the vector goes out of scope, all the derived objects are cleaned up properly without you having to manually call delete.
Now, your summation loop becomes a three-line affair. You iterate through the vector, call getArea() on each pointer, and add the result to a running total. The CPU figures out at runtime which version of the function to call based on the actual object type. Your code doesn't need to know if it's looking at a circle or a square; it just knows it's a Shape, and shapes have areas.
📋 Practical Task
Exercise: Building a Polymorphic Area Calculator for a 2D Layout Engine
Your task is to implement a shape hierarchy that allows a program to calculate the total area of a mixed collection of geometric objects.
- Create an abstract base class named
Shapewith a virtual destructor and a pure virtual functiondouble getArea() const. - Implement a
Rectangleclass that inherits fromShape. It should takewidthandheightin its constructor. - Implement a
Circleclass that inherits fromShape. It should take aradiusin its constructor (use3.14159for Pi). - Implement a
Triangleclass that inherits fromShape. It should takebaseandheightin its constructor. - In your
mainfunction:- Create a
std::vectorofstd::unique_ptr<Shape>. - Add at least one of each shape to the vector.
- Iterate through the vector using a loop to calculate and print the total combined area of all shapes.
- Create a
There are no comments for now.