Polymorphism
Call the same interface on different object types and get each object's own behavior — the mechanism behind virtual functions, abstract classes, and dynamic dispatch.
Polymorphism means a single interface can behave differently depending on the actual object involved. In C++ this shows up in two distinct forms: compile-time polymorphism, resolved while the program is being compiled, and runtime polymorphism, resolved while the program is executing, based on the actual type of the object a base pointer or reference refers to. This topic focuses on runtime polymorphism through virtual functions — how it works, how to declare it correctly, and the rules that govern it.
What Is Polymorphism?
Polymorphism lets code written in terms of a base class work correctly with any derived class, without knowing the derived type in advance. A function that accepts a Shape reference can operate on a Circle, a Square, or any other Shape-derived type, and each object responds according to its own type.
Shape defines a general area() operation; Circle supplies its own version. Both classes share the Shape interface.
The word polymorphism comes from Greek for 'many forms' — one interface, many behaviors depending on which concrete type is actually involved.
class Shape
{
public:
virtual double area() const
{
return 0.0;
}
};
class Circle : public Shape
{
public:
explicit Circle(double r) : radius(r) {}
double area() const override
{
return 3.14159 * radius * radius;
}
private:
double radius;
};Why Polymorphism Matters
Without polymorphism, code that needs to handle several related types either duplicates logic for each type or branches on a type tag (an if/else or switch on some kind of type code) every time it needs type-specific behavior. Polymorphism lets that branching happen once, inside each type's own override, instead of being repeated at every call site.
printArea works for any current or future Shape-derived type without being rewritten. shape.area() automatically runs the correct override for whatever object was actually passed in.
This is what makes polymorphism central to extensible designs: new derived types can be added later, and existing code written against the base interface keeps working with them unchanged.
void printArea(const Shape& shape)
{
std::cout << shape.area() << '\n';
}Compile-Time vs Runtime Polymorphism
C++ supports two different mechanisms that are both called polymorphism, but they are resolved at different times and work in different ways.
Both are legitimate forms of polymorphism; they solve different problems.
Templates are C++'s main tool for compile-time (generic) polymorphism — the compiler generates type-specific code for each instantiation. Templates are covered in depth on their own dedicated topic; this page focuses on runtime polymorphism through virtual functions and inheritance.
Aspect | Compile-time polymorphism | Runtime polymorphism
Mechanism | Function overloading, templates | Virtual functions
Resolved | During compilation | During program execution
Based on | Static (declared) argument types | The object's actual dynamic type
Requires inheritance | No | Yes (base/derived relationship)
Performance | No runtime dispatch overhead | Small runtime dispatch overhead
Extensible without | |
recompiling callers | No | Yes, new derived types just workStatic Binding vs Dynamic Binding
Binding is the process of deciding which function body a call actually invokes. Static binding (also called early binding) decides this at compile time, purely from the declared type of the expression. Dynamic binding (late binding) decides it at runtime, based on the actual type of the object.
greet() here is an ordinary (non-virtual) member function, so calls through ref are statically bound.
Because Base::greet is not virtual, the compiler resolves this call using ref's declared type, Base — it prints 'Base greet', even though the actual object is a Derived. This is static binding.
ref has declared type Base& greet() is not virtual, so the call is resolved at compile time from the declared type
Base::greet() runs, printing 'Base greet' Declaring greet() as virtual in Base changes this call to dynamic binding, described in the next sections — the runtime type of the object, not the declared type of the reference, then determines which override runs.
class Base
{
public:
void greet() const
{
std::cout << "Base greet\n";
}
};
class Derived : public Base
{
public:
void greet() const
{
std::cout << "Derived greet\n";
}
};
Base& ref = Derived{};ref.greet();Base-Class References and Pointers
Polymorphism is expressed through indirection: a base-class reference or pointer that actually refers to a derived-class object. A Derived object can always be bound to a Base& or Base* (upcasting), since a Derived is-a Base.
Both baseRef and basePtr refer to the same Derived object, but are viewed through the Base interface.
Polymorphism only works through this kind of indirection. A plain Base object (not a reference or pointer to something else) is always exactly a Base, regardless of what other types exist in the hierarchy — there is no derived object hiding inside a value that was declared as Base.
Derived d;
Base& baseRef = d;
Base* basePtr = &d;Declaring a Virtual Function
The virtual keyword, written in the base class, marks a member function as eligible for dynamic dispatch: calls through a base pointer or reference will resolve to the most-derived override for the actual object, instead of always calling the base version.
Animal::speak is virtual, so Dog and Cat can each override it with their own behavior.
Once a function is declared virtual in a base class, it stays virtual in every further derived class, whether or not the derived declaration repeats the virtual keyword — but repeating override (covered below) is the modern, recommended practice for clarity and safety.
class Animal
{
public:
virtual void speak() const
{
std::cout << "...\n";
}
};
class Dog : public Animal
{
public:
void speak() const override
{
std::cout << "Woof\n";
}
};
class Cat : public Animal
{
public:
void speak() const override
{
std::cout << "Meow\n";
}
};How Virtual Functions Enable Dynamic Dispatch
describe() is written once against Animal, yet each call prints the correct animal-specific message.
describe(dog) binds animal to the Dog object; speak() is virtual, so Dog::speak() runs, printing 'Woof' describe(cat) binds animal to the Cat object; Cat::speak() runs, printing 'Meow'
The same line, animal.speak(), dispatches differently depending on the actual object
This is dynamic dispatch: the specific function body that runs is chosen based on the dynamic (runtime) type of the referred-to object, not the static (declared) type of animal.
void describe(const Animal& animal)
{
animal.speak();
}
int main()
{
Dog dog;
Cat cat;
describe(dog);
describe(cat);
return 0;
}The Virtual Table (A Common Implementation Model)
The C++ standard guarantees the observable behavior of dynamic dispatch — that a virtual call resolves to the most-derived override — but it does not mandate any particular mechanism for implementing it. What follows is a common implementation model used by most compilers, shown to build intuition, not a rule of the language.
In this common implementation model, a class with virtual functions gets a hidden array of function pointers called a virtual table (vtable), one per class that declares or overrides virtual functions. Each polymorphic object carries a hidden pointer (often called a vptr) to its class's vtable. A virtual call looks up the correct function through this pointer at runtime instead of jumping to a fixed address at compile time.
Implementation-model intuition: each object's hidden vptr leads to its own class's table of overriding functions, which is why the same call resolves differently per object.
This model explains the small memory and performance cost of polymorphism (a pointer per object, one indirect call per virtual call), but a program must never be written to depend on vptr or vtable layout — those details are unspecified and vary between compilers.
Dog object Dog's vtable
+------------------+ +----------------------+
| vptr |----------->| speak -> Dog::speak() |
| (Dog's own data) | +----------------------+
+------------------+
Cat object Cat's vtable
+------------------+ +----------------------+
| vptr |----------->| speak -> Cat::speak() |
| (Cat's own data) | +----------------------+
+------------------+Function Overriding Rules
Overriding replaces a base class's virtual function with a derived class's own implementation. For a derived member function to override a base virtual function (rather than accidentally declaring an unrelated, non-overriding function with a similar name), its signature must match: same name, same parameter types, and the same const/volatile qualification on the function.
Same name, same parameter type (int), same const qualifier — this is a genuine override.
This declares a separate, unrelated function that happens to share a name with Base::update; it hides Base::update in Derived rather than overriding it, and a Base pointer will still dispatch to Base::update.
This mismatch is exactly the kind of silent bug the override specifier, covered next, is designed to catch at compile time.
class Base
{
public:
virtual void update(int amount) const;
};
class Derived : public Base
{
public:
void update(int amount) const override; // overrides — matches exactly
};class Sneaky : public Base
{
public:
void update(long amount) const; // does NOT override — different parameter typeCovariant Return Types
An override's return type must normally match the base function's return type exactly, with one exception: if the base returns a pointer or reference to some class Base, an override may return a pointer or reference to a class publicly derived from Base. This is called a covariant return type.
Circle::clone still overrides Shape::clone even though its return type is more specific, because Circle* is a valid covariant of Shape*.
Covariant returns let a caller working through Circle directly get back a Circle*, while callers working through the base Shape interface still see a Shape* — the override remains fully compatible with the base.
class Shape
{
public:
virtual Shape* clone() const
{
return new Shape(*this);
}
};
class Circle : public Shape
{
public:
Circle* clone() const override // covariant: Circle* instead of Shape*
{
return new Circle(*this);
}
};The override Specifier
override is placed after a derived member function's parameter list to state the intent 'this function is meant to override a base virtual function.' The compiler then checks that a matching virtual function actually exists in a base class, and reports an error if it does not.
If Base::render's signature ever changes and Derived::render is not updated to match, this override will fail to compile instead of silently becoming a new, non-overriding function.
override adds no runtime behavior of its own — it is purely a compile-time check — but it is strongly recommended on every intended override, because it turns the signature-mismatch problem from the previous section into a compile error instead of a silent bug.
class Base
{
public:
virtual void render() const;
};
class Derived : public Base
{
public:
void render() const override; // compiler verifies this really overrides Base::render
};The final Specifier
final can be applied to a virtual function to forbid any further derived class from overriding it, or to an entire class to forbid any further derived class from inheriting from it at all.
final on a function locks in that override as the last word for this branch of the hierarchy.
final on a class prevents any further derivation from it, which can also let a compiler safely skip dynamic dispatch for calls known to target that exact class.
final is a design tool: it documents and enforces that a particular behavior is not meant to be customized further down the hierarchy.
class Base
{
public:
virtual void step() const;
};
class Derived : public Base
{
public:
void step() const override final; // no further class may override step()
};
class MoreDerived : public Derived
{
public:
// void step() const override; // error: step() is final in Derived
};class Sealed final : public Base
{
// ...
};
// class Illegal : public Sealed {}; // error: Sealed is finalVirtual Function Default Arguments Are Statically Bound
Default arguments are resolved based on the static (declared) type of the expression used to call the function, even for virtual functions — they are not part of dynamic dispatch. This is an important edge case: only which function body runs is chosen dynamically; which default arguments apply is chosen statically.
Each class specifies its own default for msg.
This calls Derived::log (dynamic dispatch picks the override for the actual object), but with the default argument 'base' (static binding picks the default from Base, ref's declared type). Output: base.
ref's declared type is Base&, so the default argument is taken from Base::log the function body chosen is still Derived::log, because that call is dynamically dispatched the printed message is 'base', supplied by Base's default, but printed by Derived's function body
To avoid this mismatch, give overrides the same default arguments as the base function, or avoid default arguments on virtual functions and provide a non-virtual overload instead.
class Base
{
public:
virtual void log(const std::string& msg = "base") const
{
std::cout << msg << '\n';
}
};
class Derived : public Base
{
public:
void log(const std::string& msg = "derived") const override
{
std::cout << msg << '\n';
}
};Derived d;
Base& ref = d;
ref.log();Calling Virtual Functions From Constructors and Destructors
During a base class's own constructor or destructor, calling a virtual function does not dispatch to a derived class's override — even if the object being constructed or destroyed is ultimately of a derived type. While a base subobject's constructor is running, the derived part of the object either does not yet exist (during construction) or has already been destroyed (during destruction), so the object's dynamic type is treated as the base class for the duration of that constructor or destructor.
Constructing a Derived first runs Base's constructor, during which the object behaves as a Base for virtual dispatch purposes.
This is a deliberate, standard-defined rule, not a bug: it prevents a virtual call from reaching an override whose derived-class members have not been initialized yet (or have already been destroyed).
class Base
{
public:
Base()
{
announce(); // calls Base::announce here, not any override
}
virtual void announce() const
{
std::cout << "Base::announce\n";
}
};
class Derived : public Base
{
public:
void announce() const override
{
std::cout << "Derived::announce\n";
}
};
Derived d; // prints "Base::announce", not "Derived::announce"Pure Virtual Functions
A pure virtual function is declared with = 0 instead of a body (or with a body that must still be explicitly invoked, if provided). It has no default implementation that applies automatically, and it forces every concrete (instantiable) derived class to supply its own override.
area() has no meaningful default for an abstract Shape — each concrete shape must define its own.
Any class with at least one pure virtual function that is not overridden becomes an abstract class, described next.
class Shape
{
public:
virtual double area() const = 0; // pure virtual: no default behavior
virtual ~Shape() = default;
};Abstract Classes
A class that declares (or inherits, without overriding) at least one pure virtual function is an abstract class. Abstract classes cannot be instantiated directly — attempting to create an object of an abstract class is a compile error — but they can still be used as base-class references or pointers, and can still have data members, non-pure member functions, and constructors used by derived classes.
Circle supplies area(), so it is no longer abstract and can be instantiated.
Abstract classes are how C++ expresses 'this type defines a contract but has no complete, standalone behavior of its own' — they define what derived classes must be able to do, not necessarily how.
// Shape shape; // error: Shape is abstract (area() is pure virtual)
class Circle : public Shape
{
public:
explicit Circle(double r) : radius(r) {}
double area() const override
{
return 3.14159 * radius * radius;
}
private:
double radius;
};
Circle circle(2.0); // fine: Circle overrides area(), so it is concreteInterface-Style Design With Pure Abstract Classes
A class made up entirely of pure virtual functions (and typically a virtual destructor, with no data members) acts as an interface: a pure specification of behavior with no implementation of its own. This is a common, idiomatic C++ pattern for defining a contract that unrelated concrete classes can each implement independently.
Drawable is a pure interface: it says every Drawable can draw() itself, without saying how.
render() works uniformly with any current or future Drawable implementation, purely through the interface.
This interface-style design lets independent parts of a program (or independent teams) implement a shared contract without depending on each other's implementation details — only on the abstract interface.
class Drawable
{
public:
virtual void draw() const = 0;
virtual ~Drawable() = default;
};
class Button : public Drawable
{
public:
void draw() const override
{
std::cout << "drawing a button\n";
}
};
class Icon : public Drawable
{
public:
void draw() const override
{
std::cout << "drawing an icon\n";
}
};void render(const Drawable& item)
{
item.draw();
}Virtual Destructors
A base class intended to be used polymorphically through pointers — where a derived object may be deleted through a base pointer — should declare its destructor virtual. A virtual destructor ensures that deleting through a base pointer runs the complete destruction sequence: the most-derived class's destructor first, then each base class destructor in turn, up the hierarchy.
Because ~Base is virtual, delete ptr; correctly runs ~Derived() first (releasing data's resources), then ~Base().
ptr's static type is Base*, but the object's dynamic type is Derived
~Base is virtual, so delete ptr; dispatches to the most-derived destructor, ~Derived()
~Derived() runs first, printing '~Derived' and destroying data the implicit chain then runs ~Base(), printing '~Base'
Whether a destructor needs to be virtual is decided by how the class is used, not simply by whether it has virtual functions elsewhere — see the next section for the precise rule.
class Base
{
public:
virtual ~Base()
{
std::cout << "~Base\n";
}
};
class Derived : public Base
{
public:
~Derived() override
{
std::cout << "~Derived\n";
}
private:
std::vector<int> data; // owns a resource that needs proper cleanup
};
Base* ptr = new Derived();
delete ptr;Why Deleting Through a Base Pointer Without a Virtual Destructor Is Dangerous
Deleting a derived object through a pointer to a base class whose destructor is not virtual is undefined behavior — in practice, only ~Base() runs, and Derived's members (here, data) are never properly destroyed, leaking whatever resources they owned.
ptr's static type is Base*; ~Base is not virtual delete ptr; is resolved statically, calling only ~Base()
~Derived() is skipped, so data's resources are never released — this is undefined behavior, and this leak is only the commonly observed symptom
The governing rule: a class should declare its destructor virtual if it is designed to be deleted polymorphically through a base pointer. A class that is never used that way (for example, a small value type never derived from and never deleted through a base pointer) does not need a virtual destructor purely for this reason — but note that any base class with other virtual functions is a strong signal it may be used polymorphically, so its destructor should usually be virtual as well.
class Base
{
public:
~Base() // not virtual
{
std::cout << "~Base\n";
}
};
class Derived : public Base
{
public:
~Derived()
{
std::cout << "~Derived\n";
}
private:
std::vector<int> data;
};
Base* ptr = new Derived();
delete ptr; // undefined behavior: ~Derived() is never calledObject Slicing
When a derived object is assigned or copied into a variable declared with the base class type (by value, not by reference or pointer), only the base-class portion is copied. The derived-specific members are discarded — this is called object slicing.
sliced is declared as Base by value, so only Base's data (and Base's own behavior) survive the copy.
d is a full Derived object Base sliced = d; copies only the Base subobject of d into sliced — derivedValue is lost entirely sliced is a genuine Base object now, not a Derived one wearing a Base label sliced.speak() calls Base::speak(), printing 'Base', because sliced's dynamic type really is Base
Slicing is not undefined behavior — it is well-defined, ordinary base-class copying — but it silently discards the derived part and breaks polymorphism, since the sliced object genuinely no longer is a Derived. Passing and storing polymorphic objects by reference or by pointer (including smart pointers), rather than by value as the base type, avoids slicing.
class Base
{
public:
virtual void speak() const
{
std::cout << "Base\n";
}
int baseValue = 1;
};
class Derived : public Base
{
public:
void speak() const override
{
std::cout << "Derived\n";
}
int derivedValue = 2;
};
Derived d;
Base sliced = d; // object slicing: only the Base part of d is copied
sliced.speak();Passing and Storing Polymorphic Objects Correctly
Only the by-value overload risks slicing; reference and pointer parameters both preserve polymorphic behavior.
As a design rule: functions meant to operate polymorphically on a hierarchy should take the base type by reference or by pointer, never by value. Value parameters and value members are appropriate when slicing to the base type is exactly the intended behavior (rare), or when the type is not meant to be used polymorphically at all.
void handle(Base value); // slices any Derived argument
void handle(const Base& ref); // preserves the actual dynamic type
void handle(Base* ptr); // preserves the actual dynamic typePolymorphism With Containers of Owning Pointers
Storing polymorphic objects directly in a container (for example std::vector<Shape>) would slice every element down to Shape on insertion. Polymorphic collections instead store pointers — ideally owning smart pointers — to the base type, so each element keeps its own full derived type and behavior.
Each unique_ptr<Shape> owns a distinct derived object; shape->area() dispatches to the correct override for each one.
This pattern depends on Shape having a virtual destructor (covered above), since destroying the vector destroys each unique_ptr, which deletes its owned object through a Shape* — exactly the polymorphic-deletion case that requires a virtual destructor.
#include <memory>
#include <vector>
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(2.0));
shapes.push_back(std::make_unique<Square>(3.0));
for (const auto& shape : shapes)
{
std::cout << shape->area() << '\n';
}Best Practices
1. Give any base class that is used polymorphically a virtual destructor — including a pure abstract interface class, where = default is typically sufficient.
2. Mark every intended override with override, so the compiler catches signature mismatches instead of silently creating a non-overriding function.
3. Prefer pure virtual functions over a virtual function with a placeholder default body when there is no sensible default behavior — a pure virtual function forces every concrete derived class to make a deliberate choice.
4. Pass and store polymorphic objects through references or (smart) pointers to the base type, never by value, to avoid object slicing.
5. Avoid calling virtual functions from constructors or destructors when you intend derived behavior to run — the call will resolve to the currently-constructing/destructing class's own version, not a derived override.
6. Use final where a class or an override is deliberately meant to be the end of a customization point — it documents intent and can help the compiler generate more efficient code.
7. Keep default arguments consistent across a virtual function and its overrides, or avoid them on virtual functions entirely, since default arguments are chosen statically rather than dynamically.
Complete Example
A polymorphic hierarchy of shapes stored uniformly as owning pointers to the abstract base, each dispatching to its own area() through describe().
Shape is abstract because area() is pure virtual Circle and Square each override area() and are concrete, so they can be instantiated the vector holds unique_ptr<Shape>, avoiding slicing and giving each Shape a virtual destructor for safe polymorphic deletion shape->describe() calls the non-virtual-in-name-only Shape::describe(), which itself calls the virtual area() — dispatching to Circle::area() or Square::area() depending on the actual object
The program's output (exact digits may vary slightly by formatting).
#include <iostream>
#include <memory>
#include <vector>
class Shape
{
public:
virtual double area() const = 0;
virtual void describe() const
{
std::cout << "Shape with area " << area() << '\n';
}
virtual ~Shape() = default;
};
class Circle final : public Shape
{
public:
explicit Circle(double r) : radius(r) {}
double area() const override
{
return 3.14159 * radius * radius;
}
private:
double radius;
};
class Square final : public Shape
{
public:
explicit Square(double s) : side(s) {}
double area() const override
{
return side * side;
}
private:
double side;
};
int main()
{
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(2.0));
shapes.push_back(std::make_unique<Square>(3.0));
for (const auto& shape : shapes)
{
shape->describe();
}
return 0;
}Shape with area 12.5664
Shape with area 9Polymorphism Mental Model
Think of a base pointer or reference as a fixed-shape window: it only exposes the operations the base class declares, but what happens when you call through that window depends on which actual object sits behind it.
The call site never changes; the object behind the pointer determines which override actually executes.
The essential rule to internalize: static type (what the compiler sees the expression as) determines what operations are callable; dynamic type (what the object actually is) determines, for virtual functions only, which implementation runs.
Base* ptr ----> [ Circle object ] ptr->area() runs Circle::area()
Base* ptr ----> [ Square object ] ptr->area() runs Square::area()
Same call, same declared type (Base*), different behavior per object.Quick Reference
A quick-reference table summarizing the core syntax and rules from this topic.
Concept | Syntax | Meaning
Declare virtual | virtual void f() const; | Enables dynamic dispatch for f()
Override | void f() const override; | Supplies a derived implementation, checked by the compiler
Pure virtual | virtual void f() const = 0; | No default; forces derived classes to override
Abstract class | (has an unoverridden pure virtual) | Cannot be instantiated directly
Seal an override | void f() const override final; | No further class may override f()
Seal a class | class C final : public Base {}; | No further class may derive from C
Virtual destructor | virtual ~Base() = default; | Correct cleanup when deleting via Base*
Covariant return | Derived* f() const override; | Valid when base returns Base*
Slicing avoidance | const Base& / Base* / unique_ptr<Base> | Preserves the object's real dynamic typeKey Takeaways
Polymorphism lets code written against a base interface behave correctly for any derived type, resolved either at compile time (overloading, templates) or at runtime (virtual functions).
A virtual function is dispatched dynamically, based on the object's actual type, when called through a base reference or pointer; a non-virtual function is always resolved statically, from the expression's declared type.
override lets the compiler verify a derived function genuinely overrides a base virtual function; final forbids further overriding or derivation.
A pure virtual function (= 0) has no automatic default and makes its class abstract; an abstract class cannot be instantiated but can still be used polymorphically.
A base class used polymorphically through pointers needs a virtual destructor, or deleting a derived object through a base pointer is undefined behavior.
Assigning or copying a derived object into a base object by value slices off the derived part; polymorphic code should use references or pointers to the base, not base-by-value.
Default arguments on virtual functions are bound statically, not dynamically — keep them consistent across overrides to avoid surprising behavior.
The 'vtable' is a widely used implementation model for dynamic dispatch, not something the C++ standard itself requires or specifies.
Given a Base class with a non-virtual member function f() and a Derived class that redefines f() with the same signature, what does baseRef.f() print if baseRef is a Base& bound to a Derived object, and why?
It calls Base::f(), not Derived::f(). Because f() is not virtual, the call is statically bound — resolved at compile time using baseRef's declared type, Base, regardless of the object's actual type.
Why does an abstract class with a pure virtual function still allow you to declare a Shape* or Shape& even though you cannot write Shape shape;?
A pointer or reference does not require creating a Shape object by itself — it only needs to refer to some object of a type compatible with Shape, such as a concrete derived class. Only direct instantiation of the abstract type itself (Shape shape;) is forbidden, because that would require a complete, fully-defined Shape object, which cannot exist while area() (or another pure virtual function) has no implementation.
A base class Base has a virtual function but a non-virtual destructor. Code does Base* p = new Derived(); delete p; where Derived adds its own resource-owning members. What goes wrong?
Because ~Base is not virtual, delete p; is resolved statically and only calls ~Base(). ~Derived() never runs, so Derived's resource-owning members are never properly destroyed — this is undefined behavior, commonly observed as a resource leak. The fix is to declare ~Base() virtual.
Explain object slicing using Derived d; Base b = d;, and explain why passing d to a function taking const Base& does not have the same problem.
Base b = d; declares b as a Base by value, so only the Base portion of d is copied into b — any Derived-specific members are discarded, and b's dynamic type is genuinely Base from then on. Passing d to a function parameter of type const Base& instead binds a reference to the original Derived object itself; no copy is made, the object's real dynamic type is preserved, and any virtual calls through that reference still dispatch to Derived's overrides.
Why is calling a virtual function from within a base class's own constructor considered safe but not fully 'polymorphic' — that is, why doesn't it call a derived override even when constructing a Derived object?
While a base class's constructor is executing, the derived part of the object has not been initialized yet (its constructor hasn't run), so the object is treated as being of the base type for virtual dispatch during that time. This avoids calling into a derived override that might depend on not-yet-initialized derived members. As a result, the call always resolves to the base class's own version (or an override from a class already fully constructed at that point), never to a not-yet-constructed derived class's override.
Trace this three-level hierarchy. What does the program print, and why does show(), written only in terms of Base, reach Leaf::who()?
It prints 'Leaf'. who() is declared virtual in Base and overridden in both Mid and Leaf. show() takes a const Base&, but a reference only restricts which declared operations are visible — it does not change the object's actual dynamic type. Since leaf's dynamic type is Leaf, and who() is virtual, the call b.who() dispatches to the most-derived override available for that object, which is Leaf::who(), regardless of how many levels of inheritance separate Leaf from Base.
Write an abstract class Notifier with a pure virtual function send(const std::string& message) const and a virtual destructor. Derive an EmailNotifier that overrides send() to print 'Email: ' followed by the message. In main, store an EmailNotifier in a std::unique_ptr<Notifier> and call send() through it with the message 'Build finished'.
Notifier is abstract because send() is pure virtual, so it cannot be instantiated directly, but a unique_ptr<Notifier> can still own an EmailNotifier object. notifier->send(...) dispatches dynamically to EmailNotifier::send(), printing 'Email: Build finished'. The virtual destructor ensures that when the unique_ptr goes out of scope, it correctly destroys the EmailNotifier object through the Notifier* it holds internally.
What does this program print? Explain how the chosen default argument and the chosen function body are determined differently.
It prints 2. Which function body runs is determined dynamically: ref's dynamic type is DoubleCounter, and bump() is virtual, so DoubleCounter::bump() executes. Which default argument is used is determined statically: ref's declared type is Counter&, so the default argument comes from Counter::bump's declaration, by = 1, not DoubleCounter::bump's by = 10. So the call effectively runs as DoubleCounter::bump(1), and DoubleCounter::bump returns by * 2, giving 1 * 2 = 2.
Write a function template-free (ordinary) function named totalArea that takes a std::vector<std::unique_ptr<Shape>> by const reference (assume Shape is an abstract class with a pure virtual double area() const and a virtual destructor) and returns the sum of every shape's area(). Show it used with two shapes.
totalArea iterates the vector by const reference (no copying, so no slicing risk), and shape->area() dispatches dynamically to each stored object's own override. With the two rectangles shown, area() returns 6.0 and 20.0, so totalArea returns 26.0. Because Shape is abstract with a virtual destructor, the vector can safely own heterogeneous derived shapes through unique_ptr<Shape> without slicing and with correct cleanup on destruction.
class Base
{
public:
virtual void who() const { std::cout << "Base\n"; }
};
class Mid : public Base
{
public:
void who() const override { std::cout << "Mid\n"; }
};
class Leaf : public Mid
{
public:
void who() const override { std::cout << "Leaf\n"; }
};
void show(const Base& b) { b.who(); }
int main()
{
Leaf leaf;
show(leaf);
return 0;
}class Counter
{
public:
virtual int bump(int by = 1) const { return by; }
};
class DoubleCounter : public Counter
{
public:
int bump(int by = 10) const override { return by * 2; }
};
int main()
{
DoubleCounter dc;
Counter& ref = dc;
std::cout << ref.bump();
return 0;
}