Object-Oriented Programming in C++¶
C++ supports runtime polymorphism through inheritance and virtual functions, but also supports value-based and compile-time polymorphism. Use the mechanism that matches the required substitution and ownership model.
Runtime interface¶
class shape {
public:
virtual ~shape() = default;
[[nodiscard]] virtual double area() const = 0;
};
class circle final : public shape {
public:
explicit circle(double radius) : radius_{radius} {
if (!(radius >= 0.0)) throw std::invalid_argument{"invalid radius"};
}
[[nodiscard]] double area() const override {
return std::numbers::pi * radius_ * radius_;
}
private:
double radius_;
};
The virtual destructor makes deletion through a base pointer safe. override
asks the compiler to verify the intended override. final prevents further
derivation from circle.
Substitutability¶
Public inheritance should normally mean “is substitutable for,” not merely “can reuse code from.” A derived type must preserve the base contract: accepted input, promised output, invariants, failure behavior, and relevant complexity or lifetime expectations.
Protected implementation inheritance creates coupling to base internals. Prefer composition when a type only needs another object's behavior.
Object slicing¶
Passing or storing a derived object by base value copies only the base subobject:
void inspect(shape value); // impossible here because shape is abstract;
// a concrete base would slice derived state
Polymorphic objects are normally accessed through references or owning smart pointers. Containers of base values do not preserve derived dynamic types.
Multiple inheritance¶
Multiple inheritance can combine independent pure interfaces. Multiple implementation bases introduce ambiguity, complex construction, and possible diamond-shaped base subobjects. Virtual inheritance solves a particular shared- base representation problem but adds semantic and implementation complexity.
Static polymorphism¶
Templates and concepts can express polymorphism without a shared runtime base. This enables optimization and value semantics but may increase compile time, binary size, and diagnostic complexity. Runtime and static polymorphism solve different deployment and extensibility problems.
Common mistakes¶
- missing virtual destructor in a polymorphic base meant for owning deletion;
- forgetting
overrideand accidentally declaring a new overload; - inheritance solely for implementation reuse;
- copying polymorphic objects by base value;
- exposing owning raw pointers with unclear deletion responsibility;
- assuming virtual dispatch during base construction reaches the derived override.
Exercises¶
- Refactor an inheritance relationship that exists only to reuse a helper method.
- Explain when a
std::variantis preferable to an open virtual hierarchy. - State the ownership policy of
std::vector<std::unique_ptr<shape>>.