Virtual Constructor / Clone Idiom
Virtual constructor idiom in C++ — polymorphic object copying via clone(), covariant returns, CRTP mixin, and prototype factory patterns.
Virtual Constructor / Clone Idiomsince C++98A design idiom that simulates virtual construction by delegating object copying or default-creation to a virtual member function (clone() / create()), enabling a caller to duplicate a polymorphic object through a base pointer without knowing its concrete type.
Overview
C++ constructors are inherently non-virtual. The vtable pointer is installed by the compiler during construction, so the object's dynamic type is not fully established until the constructor body completes. Declaring a constructor virtual would be circular: the vtable is needed to dispatch the call, but the constructor must run first to build the vtable.
The practical consequence is object slicing. Copying through a base reference copies only the base subobject — the derived portion is silently discarded:
struct Base { int x = 0; virtual ~Base() = default; };
struct Derived : Base { int y = 0; };
std::unique_ptr<Base> obj = std::make_unique<Derived>(); // C++11
Base copy = *obj; // slices: y is lost, no warning, no errorThe virtual constructor idiom resolves this by delegating copying to a virtual member function — called clone() by convention — that each concrete class overrides to return a fully-typed, heap-allocated copy of itself.
Since C++11, the canonical return type is std::unique_ptr<Base>, which expresses ownership unambiguously and prevents leaks at throw sites. Pre-C++11 codebases use raw pointer returns instead.
Syntax
Canonical C++11 form — unique_ptr return:
class Base {
public:
virtual ~Base() = default; // mandatory
virtual std::unique_ptr<Base> clone() const = 0; // C++11
};
class Derived : public Base {
public:
std::unique_ptr<Base> clone() const override { // C++11
return std::make_unique<Derived>(*this); // invokes Derived copy ctor
}
};C++98 covariant raw pointer form:
class Base {
public:
virtual ~Base() {}
virtual Base* clone() const = 0;
};
class Derived : public Base {
public:
Derived* clone() const override { // covariant return type — C++98
return new Derived(*this);
}
};Covariant return types — where an override may return a pointer or reference to a type derived from the base function's declared return type — were standardised in C++98. They let a caller that already knows the concrete type avoid a dynamic_cast. Covariance does not extend to class templates, so std::unique_ptr<Derived> is not covariant with std::unique_ptr<Base>. This is the single most common misconception with this idiom.
A sibling virtual function, create(), simulates a virtual default constructor:
class Base {
public:
virtual std::unique_ptr<Base> clone() const = 0; // copy-construct peer
virtual std::unique_ptr<Base> create() const = 0; // default-construct peer
};create() is useful in prototype-based factories where you want a fresh, zero-state sibling of an existing object without knowing its type.
Examples
CRTP Clone Mixin
Writing an identical clone() body in every concrete class is error-prone boilerplate. A CRTP mixin injects it once:
#include <memory>
class Shape {
public:
virtual ~Shape() = default;
virtual std::unique_ptr<Shape> clone() const = 0;
virtual double area() const = 0;
};
// CRTP mixin — C++11; requires Derived to be copy-constructible
template<typename Derived, typename Base>
class Cloneable : public Base {
public:
std::unique_ptr<Base> clone() const override {
return std::make_unique<Derived>(
static_cast<const Derived&>(*this)
);
}
};
class Circle : public Cloneable<Circle, Shape> {
double radius_;
public:
explicit Circle(double r) : radius_(r) {}
double area() const override { return 3.14159265358979 * radius_ * radius_; }
};
class Rectangle : public Cloneable<Rectangle, Shape> {
double w_, h_;
public:
Rectangle(double w, double h) : w_(w), h_(h) {}
double area() const override { return w_ * h_; }
};Any new concrete shape inheriting Cloneable<T, Shape> gets clone() for free. The static_cast is safe because the mixin's this is always a Derived — the CRTP guarantee.
Deep-Copying Polymorphic Containers
std::unique_ptr is move-only, so a container of unique_ptr<Base> has its copy constructor implicitly deleted. clone() restores copyability with full type fidelity:
#include <vector>
#include <memory>
class Canvas {
std::vector<std::unique_ptr<Shape>> shapes_;
public:
void add(std::unique_ptr<Shape> s) {
shapes_.push_back(std::move(s));
}
Canvas(const Canvas& other) {
shapes_.reserve(other.shapes_.size());
for (const auto& s : other.shapes_)
shapes_.push_back(s->clone()); // each element keeps its concrete type
}
Canvas(Canvas&&) noexcept = default; // C++11 move is free
Canvas& operator=(Canvas other) noexcept { // copy-and-swap
std::swap(shapes_, other.shapes_);
return *this;
}
};Covariant Return for Typed Callers
When the caller already has the concrete type, a covariant raw pointer return eliminates the downcast:
class Widget {
public:
virtual ~Widget() = default;
virtual Widget* clone() const { return new Widget(*this); }
};
class Button : public Widget {
std::string label_;
public:
explicit Button(std::string label) : label_(std::move(label)) {}
Button* clone() const override { return new Button(*this); } // covariant — C++98
const std::string& label() const { return label_; }
};
Button b{"Submit"};
std::unique_ptr<Button> copy(b.clone()); // typed, no cast needed
copy->label(); // accessible directlyWrap the raw covariant return in unique_ptr immediately at the call site. Reserve this form for legacy APIs or for cases where the concrete return type is genuinely needed by the caller.
Prototype-Based Factory
clone() is the mechanism behind the prototype design pattern — register exemplar objects and clone them on demand, decoupling creation from type knowledge:
#include <unordered_map>
#include <stdexcept>
class Enemy {
public:
virtual ~Enemy() = default;
virtual std::unique_ptr<Enemy> clone() const = 0;
virtual void update() = 0;
};
class EnemyRegistry {
std::unordered_map<std::string, std::unique_ptr<Enemy>> prototypes_;
public:
void register_type(std::string key, std::unique_ptr<Enemy> proto) {
prototypes_[std::move(key)] = std::move(proto);
}
std::unique_ptr<Enemy> spawn(const std::string& key) const {
auto it = prototypes_.find(key);
if (it == prototypes_.end())
throw std::invalid_argument("Unknown enemy type: " + key);
return it->second->clone();
}
};New Enemy subtypes can be registered at runtime without modifying the factory. The registry's prototype objects carry configuration (HP, speed, behaviour flags) that gets cloned into every spawned instance.
Best Practices
- Always declare the destructor virtual. Without it,
delete base_ptron a derived object is undefined behaviour.clone()without a virtual destructor is a memory leak waiting to happen. - Return
std::unique_ptr<Base>, not a raw pointer (C++11+). Ownership is unambiguous, and leaks at throw sites are impossible. - Mark
clone()const. Cloning must not modify the source, andconstprevents it while allowing cloning fromconstreferences and views. - Use
Cloneable<>for hierarchies with many leaf types. Repeating an identical one-liner in each class is noise and a source of silent copy-paste bugs (wrong class name inmake_unique). - Redeclare
clone()as pure at every abstract layer. If an intermediate abstract class inheritsclone()without redeclaring it= 0, a new concrete subclass that forgets to override it will silently invoke the intermediate's implementation and return the wrong type. Keeping= 0propagated ensures a missing override is a compile error.
Common Pitfalls
Expecting covariant returns to work with unique_ptr.
This is the most common mistake. unique_ptr<Derived> is a completely unrelated type to unique_ptr<Base>:
class Derived : public Base {
// ERROR: return type is not covariant with std::unique_ptr<Base>
std::unique_ptr<Derived> clone() const override { ... }
};The fix is to keep the return type std::unique_ptr<Base> and let the caller dynamic_cast if it needs the derived type, or use a separate covariant raw-pointer helper wrapped immediately in unique_ptr.
Slicing through dereferenced clones.
clone() only preserves the type when the result is stored as a pointer or smart pointer to the base:
Base copy1 = *ptr->clone(); // slices — dereferences into Base
std::unique_ptr<Base> copy2 = ptr->clone(); // correctNon-copyable members.
If a derived class holds a std::mutex, open file handle, or any other non-copyable member, its copy constructor is deleted and make_unique<Derived>(*this) will not compile. Either provide a custom copy constructor that reinitialises such members to a sensible default, or explicitly document (and = delete) that the class is not cloneable.
Silent wrong-type returns in multi-level hierarchies.
In a three-level hierarchy A → B → C, if B is abstract and re-declares clone() as = 0 but provides a default implementation (a legal C++ pattern), and C forgets to override, it silently runs B::clone() and returns a B. Enable -Wsuggest-override (GCC/Clang) and use override consistently to catch this at compile time.
See Also
- CRTP — the curiously recurring template pattern that powers
Cloneable<> - RAII — smart pointers returned by
clone()follow RAII ownership - Copy-and-Swap — canonical assignment operator pattern, often paired with
clone()-based copy constructors - Type Erasure — an alternative when value semantics for polymorphic types are preferred over pointer-based ownership