Skip to content
C++
Idiom
since C++98
Intermediate

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++98

A 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:

cpp
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 error

The 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:

cpp
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:

cpp
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:

cpp
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:

cpp
#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:

cpp
#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:

cpp
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 directly

Wrap 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:

cpp
#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_ptr on 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, and const prevents it while allowing cloning from const references 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 in make_unique).
  • Redeclare clone() as pure at every abstract layer. If an intermediate abstract class inherits clone() 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 = 0 propagated 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>:

cpp
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:

cpp
Base copy1 = *ptr->clone();                    // slices — dereferences into Base
std::unique_ptr<Base> copy2 = ptr->clone();    // correct

Non-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