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.

Combining with factory method

The virtual constructor and factory method patterns complement each other naturally. A factory method centralises creation decisions; clone() handles duplication once an object already exists. Together they cover the full lifecycle of a polymorphic object.

cpp
#include <memory>
#include <string>

class Document {
public:
    virtual ~Document() = default;
    virtual std::unique_ptr<Document> clone()  const = 0;  // C++11
    virtual std::unique_ptr<Document> create() const = 0;  // "virtual ctor"
    virtual void print() const = 0;
};

class PdfDocument : public Document {
    std::string path_;
public:
    explicit PdfDocument(std::string p) : path_(std::move(p)) {}

    std::unique_ptr<Document> clone()  const override {
        return std::make_unique<PdfDocument>(*this);        // C++11
    }
    std::unique_ptr<Document> create() const override {
        return std::make_unique<PdfDocument>("<untitled>");
    }
    void print() const override { /* render PDF */ }
};

// Factory stores a prototype; callers never name PdfDocument
class DocumentFactory {
    std::unique_ptr<Document> proto_;
public:
    explicit DocumentFactory(std::unique_ptr<Document> p)
        : proto_(std::move(p)) {}

    std::unique_ptr<Document> open(const std::string& /*path*/) const {
        auto doc = proto_->create();  // new blank instance of the right type
        // load from path ...
        return doc;
    }
    std::unique_ptr<Document> duplicate(const Document& src) const {
        return src.clone();           // full copy preserving concrete type
    }
};

create() avoids needing a path at factory-construction time while still returning the correct concrete type.

Covariant return types

Covariant return types let an override declare a more-derived pointer type without changing the call interface. This is useful when the caller already knows the concrete type and wants to avoid a cast.

cpp
class Animal {
public:
    virtual ~Animal() = default;
    virtual Animal* clone() const = 0;          // base return type
};

class Dog : public Animal {
    std::string name_;
public:
    explicit Dog(std::string n) : name_(std::move(n)) {}

    Dog* clone() const override {               // covariant — C++98
        return new Dog(*this);
    }
    const std::string& name() const { return name_; }
};

void typed_use(const Dog& d) {
    std::unique_ptr<Dog> copy(d.clone());       // no dynamic_cast needed
    copy->name();                               // Dog API accessible directly
}

void generic_use(const Animal& a) {
    std::unique_ptr<Animal> copy(a.clone());    // polymorphic, no cast
}

Wrap the raw covariant pointer in a unique_ptr immediately at the call site — never pass it through code that might throw. Covariance only works with pointers and references, never with value types or smart pointer wrappers.

Clone with unique_ptr

Returning std::unique_ptr<Base> from clone() is the idiomatic C++11 approach because ownership is explicit and the pointer cannot be accidentally ignored.

cpp
#include <memory>

class Node {
public:
    int value{};
    virtual ~Node() = default;
    virtual std::unique_ptr<Node> clone() const = 0;    // C++11
};

class LeafNode : public Node {
public:
    explicit LeafNode(int v) { value = v; }

    std::unique_ptr<Node> clone() const override {
        return std::make_unique<LeafNode>(*this);        // C++11
    }
};

class InternalNode : public Node {
    std::unique_ptr<Node> left_, right_;
public:
    InternalNode(std::unique_ptr<Node> l, std::unique_ptr<Node> r)
        : left_(std::move(l)), right_(std::move(r)) {}

    // Deep clone: recursively clone the subtree                         C++11
    std::unique_ptr<Node> clone() const override {
        auto copy = std::make_unique<InternalNode>(
            left_  ? left_->clone()  : nullptr,
            right_ ? right_->clone() : nullptr
        );
        copy->value = value;
        return copy;
    }
};

Recursive clone() implementations naturally produce deep copies of tree or graph structures. Beware of cycles — add a visited set if the object graph can have back-edges.

Virtual copy and move semantics

A class hierarchy with clone() also needs correct copy and move semantics at the base level. The base destructor must be virtual; the base copy constructor should either delegate to clone() or be documented as slice-free by design.

cpp
#include <memory>

class Widget {
public:
    virtual ~Widget() = default;

    // Prevent accidental base-level copies that would slice
    Widget(const Widget&)            = default;
    Widget& operator=(const Widget&) = default;
    Widget(Widget&&)                 = default;     // C++11
    Widget& operator=(Widget&&)      = default;     // C++11

    virtual std::unique_ptr<Widget> clone() const = 0;
};

// Owning handle that preserves polymorphic identity through copies    C++11
class WidgetHandle {
    std::unique_ptr<Widget> ptr_;
public:
    explicit WidgetHandle(std::unique_ptr<Widget> w) : ptr_(std::move(w)) {}

    // Clone-based copy — deep, type-preserving
    WidgetHandle(const WidgetHandle& o)
        : ptr_(o.ptr_ ? o.ptr_->clone() : nullptr) {}

    WidgetHandle& operator=(WidgetHandle o) noexcept {
        std::swap(ptr_, o.ptr_);                    // copy-and-swap
        return *this;
    }

    WidgetHandle(WidgetHandle&&) noexcept = default;
    WidgetHandle& operator=(WidgetHandle&&) noexcept = default;

    Widget*       get()       noexcept { return ptr_.get(); }
    const Widget* get() const noexcept { return ptr_.get(); }
};

WidgetHandle behaves like a regular value type: copyable, movable, and default-destructible, while keeping the full derived type alive across copies.

Prototype registry pattern

A prototype registry decouples creation logic from type names at runtime. Objects register themselves by string key; callers create instances without including concrete class headers.

cpp
#include <unordered_map>
#include <memory>
#include <string>
#include <stdexcept>
#include <functional>

class Serializable {
public:
    virtual ~Serializable() = default;
    virtual std::unique_ptr<Serializable> clone()         const = 0; // C++11
    virtual std::string                   type_name()     const = 0;
    virtual void                          deserialize(const std::string&) = 0;
};

class PrototypeRegistry {
    std::unordered_map<std::string, std::unique_ptr<Serializable>> table_;
public:
    void register_proto(std::unique_ptr<Serializable> proto) {
        auto key = proto->type_name();
        table_[std::move(key)] = std::move(proto);
    }

    std::unique_ptr<Serializable> create(const std::string& type_name) const {
        auto it = table_.find(type_name);
        if (it == table_.end())
            throw std::out_of_range("Unknown type: " + type_name);
        return it->second->clone();
    }

    // Convenience: deserialise from a (type, data) pair
    std::unique_ptr<Serializable> load(const std::string& type,
                                       const std::string& data) const {
        auto obj = create(type);
        obj->deserialize(data);
        return obj;
    }
};

Register once at program startup (or via a static initialiser), then call registry.load("Point3D", raw_bytes) without knowing the concrete class. This pattern is common in plugin systems and network protocol handlers.

Type-safe heterogeneous containers

clone() enables a value-semantic, type-safe heterogeneous container that avoids void* and std::any indirection. The container stores unique_ptr<Base>, forwards copies through clone(), and guarantees every element retains its concrete type.

cpp
#include <vector>
#include <memory>
#include <algorithm>

template<typename Base>
class PolymorphicVec {
    std::vector<std::unique_ptr<Base>> items_;
public:
    // Copy — each element cloned to preserve derived type              C++11
    PolymorphicVec(const PolymorphicVec& o) {
        items_.reserve(o.items_.size());
        for (const auto& p : o.items_)
            items_.push_back(p->clone());
    }

    PolymorphicVec& operator=(PolymorphicVec o) noexcept {
        std::swap(items_, o.items_);
        return *this;
    }

    PolymorphicVec(PolymorphicVec&&) noexcept = default;
    PolymorphicVec& operator=(PolymorphicVec&&) noexcept = default;

    void push_back(std::unique_ptr<Base> item) {
        items_.push_back(std::move(item));
    }

    template<typename Derived, typename... Args>
    void emplace_back(Args&&... args) {             // C++11
        items_.push_back(
            std::make_unique<Derived>(std::forward<Args>(args)...)
        );
    }

    std::size_t size() const noexcept { return items_.size(); }
    const Base& operator[](std::size_t i) const { return *items_[i]; }
    Base&       operator[](std::size_t i)       { return *items_[i]; }
};

Use emplace_back<Circle>(5.0) or emplace_back<Rectangle>(3.0, 4.0) to add elements; operator[] returns a reference to the base interface. Copying the container produces a deep, type-preserving clone of every element.

Virtual constructor vs std::variant

std::variant is a type-safe alternative to virtual dispatch that is often faster and stack-allocated. The right choice depends on whether the set of types is closed (known at compile time) or open (extensible by downstream code).

cpp
#include <variant>
#include <memory>
#include <string>

// --- Closed set: prefer std::variant (C++17) ----------------------------

struct Circle    { double radius; };
struct Rectangle { double w, h; };
struct Triangle  { double base, height; };

using Shape = std::variant<Circle, Rectangle, Triangle>;

double area(const Shape& s) {                              // C++17
    return std::visit([](const auto& x) -> double {
        using T = std::decay_t<decltype(x)>;
        if constexpr (std::is_same_v<T, Circle>)
            return 3.14159265 * x.radius * x.radius;
        else if constexpr (std::is_same_v<T, Rectangle>)
            return x.w * x.h;
        else
            return 0.5 * x.base * x.height;
    }, s);
}

// Copying a variant is trivial — no clone() needed
Shape copy_shape(const Shape& s) { return s; }

// --- Open set: prefer virtual + clone() ---------------------------------

class Plugin {
public:
    virtual ~Plugin() = default;
    virtual std::unique_ptr<Plugin> clone() const = 0;    // C++11
    virtual void execute() = 0;
};
// Third-party code can subclass Plugin without touching the variant list

Prefer std::variant when the type set is small and closed; prefer the virtual constructor idiom when the type set must be open for extension (plugin systems, polymorphic libraries, virtual method tables with many overridable behaviours).

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