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

Factory Pattern

Factory functions, registry factories, abstract factories, and static create() in modern C++ — ownership, registration, and compile-time vs runtime dispatch.

Factory Patternsince C++11

A factory separates object construction from the call site, returning a polymorphic object through a base-class pointer — in modern C++, always via std::unique_ptr<Base>.

Overview

Three distinct patterns share the "factory" name and are frequently confused:

  • Factory function — a free function or static method returning unique_ptr<Base>. The simplest and most common form.
  • Factory method — a virtual method on an abstract class that subclasses override to control which concrete type is produced. Warranted only when the factory object itself must be polymorphic.
  • Abstract factory — an interface whose methods collectively produce a family of related objects (e.g. make_button(), make_checkbox()).

Before C++11, factories returned raw Base*, leaving ownership implicit and cleanup error-prone. Since C++11, std::unique_ptr<Base> is the canonical return type. A caller that needs shared ownership can convert: std::shared_ptr<Base> sp = factory() — the reverse is not possible, so starting with unique_ptr preserves flexibility.

cpp
// Pre-C++11 (avoid): caller owns raw pointer, nothing enforces it
Base* make_thing(int kind);

// C++11+: ownership is unambiguous
std::unique_ptr<Base> make_thing(int kind);          // C++11

Factory Function

The simplest form: a free function that selects a concrete type and returns it. Prefer std::make_unique (C++14) over new inside factory bodies — it is exception-safe and co-locates the allocation with the type.

cpp
#include <memory>       // C++11
#include <string_view>  // C++17
#include <stdexcept>
#include <format>       // C++20

class Compressor {
public:
    virtual ~Compressor() = default;
    virtual std::vector<uint8_t> compress(const uint8_t* data, size_t len) = 0;
};

class ZstdCompressor : public Compressor {
public:
    std::vector<uint8_t> compress(const uint8_t* data, size_t len) override { /* ... */ return {}; }
};

class Lz4Compressor : public Compressor {
public:
    std::vector<uint8_t> compress(const uint8_t* data, size_t len) override { /* ... */ return {}; }
};

std::unique_ptr<Compressor> make_compressor(std::string_view algorithm) {
    if (algorithm == "zstd") return std::make_unique<ZstdCompressor>(); // C++14
    if (algorithm == "lz4")  return std::make_unique<Lz4Compressor>();
    throw std::invalid_argument(
        std::format("unknown algorithm: {}", algorithm));               // C++20
}

Registry Factory (Self-Registering)

When concrete types live in separate translation units or arrive as plugins, a registry eliminates the if/else chain and allows each type to declare itself at startup.

cpp
#include <functional>     // C++11
#include <unordered_map>  // C++11
#include <memory>
#include <string>

class Shape { public: virtual ~Shape() = default; virtual void draw() = 0; };

class ShapeRegistry {
public:
    using Creator = std::function<std::unique_ptr<Shape>()>; // C++11

    static ShapeRegistry& instance() {
        static ShapeRegistry reg; // Meyers singleton — thread-safe since C++11
        return reg;
    }

    // Returns false if name was already registered (no double-registration)
    bool register_type(std::string name, Creator creator) {
        return registry_.emplace(std::move(name), std::move(creator)).second;
    }

    std::unique_ptr<Shape> create(std::string_view name) const {
        auto it = registry_.find(std::string{name});
        if (it == registry_.end())
            throw std::invalid_argument(
                std::format("unknown shape: {}", name));     // C++20
        return it->second();
    }

private:
    std::unordered_map<std::string, Creator> registry_;
};

template<typename T>
struct ShapeRegistrar {
    explicit ShapeRegistrar(std::string name) {
        ShapeRegistry::instance().register_type(
            std::move(name), [] { return std::make_unique<T>(); }); // C++14
    }
};

// In circle.h — inline static runs once per program, tied to the class
class Circle : public Shape {
public:
    void draw() override { /* ... */ }
private:
    static inline ShapeRegistrar<Circle> reg_{"circle"}; // C++17 inline static
};

inline static data members (C++17) are preferable to namespace-scope statics for registrars: the single definition lives in the header, initialization is tied to the class, and the ODR guarantee prevents duplicates across TUs.

Linker dead-stripping caveat: if Circle is compiled into a static library and no other code references Circle directly, the linker may discard the entire TU, silently dropping the registration. Link with --whole-archive / /WHOLEARCHIVE for plugin-style registries, or assert registry contents at startup.


Abstract Factory

Creates families of related objects while keeping consumers decoupled from every concrete type. Most valuable when the set of product types is stable but the number of families grows:

cpp
class Button   { public: virtual ~Button()   = default; virtual void paint() = 0; };
class Slider   { public: virtual ~Slider()   = default; virtual void paint() = 0; };

class Theme {
public:
    virtual ~Theme() = default;
    virtual std::unique_ptr<Button> make_button() = 0;
    virtual std::unique_ptr<Slider> make_slider()  = 0;
};

class DarkTheme : public Theme {
public:
    std::unique_ptr<Button> make_button() override { return std::make_unique<DarkButton>(); }
    std::unique_ptr<Slider> make_slider()  override { return std::make_unique<DarkSlider>(); }
};

// Consumer sees only Theme& — zero concrete type knowledge
void render_settings_panel(Theme& theme) {
    auto ok  = theme.make_button();
    auto vol = theme.make_slider();
    ok->paint();
    vol->paint();
}

Static create() Method

Keeps the constructor private to enforce invariants or a specific allocation strategy. Use std::shared_ptr here only when shared ownership is genuinely required by the type's semantics:

cpp
class DbConnection {
    DbConnection(std::string_view dsn, int pool_size)
        : dsn_{std::string{dsn}}, pool_size_{pool_size} {}

    std::string dsn_;
    int pool_size_;

public:
    static std::shared_ptr<DbConnection>
    create(std::string_view dsn, int pool_size) {
        if (pool_size < 1 || pool_size > 128)
            throw std::invalid_argument("pool_size out of range [1, 128]");
        // std::make_shared cannot reach a private constructor;
        // use new directly and wrap it.
        return std::shared_ptr<DbConnection>(
            new DbConnection(dsn, pool_size));
    }
};

auto db = DbConnection::create("postgresql://localhost/mydb", 8);

To use std::make_shared with a private constructor, expose a private passkey tag type as a public constructor parameter that only create() can supply — this avoids the separate allocation cost of wrapping new in shared_ptr. See the Passkey section below for the full pattern.


Prototype Factory (Virtual Clone)

When you need to copy a polymorphic object without knowing its concrete type, the virtual clone pattern provides a factory built into the object itself. The base class declares a pure virtual clone() method; each derived class implements it by copy-constructing itself into a fresh heap allocation.

cpp
#include <memory>   // C++11
#include <string>
#include <unordered_map>

class Shape {
public:
    virtual ~Shape() = default;
    // Each derived class returns a deep copy of itself          // C++11
    virtual std::unique_ptr<Shape> clone() const = 0;
    virtual void draw() const = 0;
};

class Circle : public Shape {
public:
    explicit Circle(double radius) : radius_{radius} {}

    std::unique_ptr<Shape> clone() const override {
        return std::make_unique<Circle>(*this); // copy-constructor // C++14
    }

    void draw() const override { /* render circle with radius_ */ }

private:
    double radius_;
};

class Rectangle : public Shape {
public:
    Rectangle(double w, double h) : width_{w}, height_{h} {}

    std::unique_ptr<Shape> clone() const override {
        return std::make_unique<Rectangle>(*this);              // C++14
    }

    void draw() const override { /* render rectangle */ }

private:
    double width_, height_;
};

A PrototypeRegistry stores canonical prototype instances and produces copies on request. This is useful when shapes carry expensive pre-computed state that should be shared as a starting point but not mutated in common.

cpp
class PrototypeRegistry {
public:
    void register_prototype(std::string key, std::unique_ptr<Shape> proto) {
        prototypes_[std::move(key)] = std::move(proto);
    }

    // Returns a fresh clone — caller owns it exclusively          // C++11
    std::unique_ptr<Shape> create(std::string_view key) const {
        auto it = prototypes_.find(std::string{key});
        if (it == prototypes_.end())
            throw std::invalid_argument(
                std::format("unknown prototype: {}", key));     // C++20
        return it->second->clone();
    }

private:
    std::unordered_map<std::string, std::unique_ptr<Shape>> prototypes_;
};

// Usage
PrototypeRegistry reg;
reg.register_prototype("unit-circle",    std::make_unique<Circle>(1.0));
reg.register_prototype("unit-rectangle", std::make_unique<Rectangle>(1.0, 1.0));

auto c = reg.create("unit-circle");      // independent Circle copy
auto r = reg.create("unit-rectangle");  // independent Rectangle copy

Each derived class must be copy-constructible for std::make_unique<Derived>(*this) to compile. If a derived class acquires non-copyable resources (e.g. a file descriptor), it must either deep-copy them explicitly in the copy constructor or delete the copy constructor and provide a custom clone() that constructs a fresh instance from the relevant data fields instead.


Factory with Constructor Arguments (Forwarding Factory)

A zero-argument registry covers many use cases, but real objects often require construction parameters. A template helper can forward arbitrary arguments to any concrete type:

cpp
#include <memory>       // C++11
#include <utility>      // C++11  (std::forward)

// Free function: create any T with arbitrary constructor args  // C++14
template<typename T, typename Base, typename... Args>
std::unique_ptr<Base> make(Args&&... args) {
    return std::make_unique<T>(std::forward<Args>(args)...);    // C++14
}

// Convenience alias when Base is known
template<typename T, typename... Args>
std::unique_ptr<Compressor> make_compressor(Args&&... args) {
    return std::make_unique<T>(std::forward<Args>(args)...);    // C++14
}

auto zstd = make_compressor<ZstdCompressor>(/*level=*/3);

Storing typed creators in a registry is harder because std::function cannot represent a heterogeneous argument list uniformly. The two practical solutions are:

Option A — std::any-based creators. Each creator is stored as std::any; the caller must std::any_cast to the right std::function specialisation before invoking. This is maximally flexible but sacrifices compile-time type safety at the call site.

Option B — typed register_<T>() methods. Each factory variant is a separate overload or method on the registry, typed to the expected argument list. The compiler enforces argument types; adding a new type requires a new register_* overload.

cpp
#include <any>          // C++17
#include <functional>   // C++11
#include <unordered_map>
#include <string>

// Option A: any-based heterogeneous registry                   // C++17
class CompressorRegistry {
public:
    template<typename T, typename... Args>
    void register_type(std::string key) {
        creators_[key] = std::function<std::unique_ptr<Compressor>(Args...)>{
            [](Args... args) {
                return std::make_unique<T>(std::forward<Args>(args)...); // C++14
            }
        };
    }

    // Caller must know the exact argument types                 // C++17
    template<typename... Args>
    std::unique_ptr<Compressor> create(std::string_view key, Args&&... args) const {
        auto it = creators_.find(std::string{key});
        if (it == creators_.end())
            throw std::invalid_argument(
                std::format("unknown compressor: {}", key));    // C++20
        using FnType = std::function<std::unique_ptr<Compressor>(Args...)>;
        return std::any_cast<const FnType&>(it->second)(
            std::forward<Args>(args)...);
    }

private:
    std::unordered_map<std::string, std::any> creators_;        // C++17
};

CompressorRegistry reg;
reg.register_type<ZstdCompressor, int>("zstd");     // int = compression level
auto c = reg.create("zstd", 3);

For most production registries, Option B — typed register_<T>() methods with a fixed argument list per product family — is simpler to reason about and produces better compiler diagnostics.


Factory + Builder Combination

For objects with many optional parameters, the most ergonomic production pattern is a factory that returns a Builder rather than the finished object. The factory validates identity and wires up required dependencies; the Builder collects optional configuration through a fluent interface and enforces invariants only at the final build() call.

cpp
#include <memory>       // C++11
#include <string>
#include <stdexcept>

class Connection {
public:
    // Full parameter set; construction only via ConnectionBuilder
    struct Config {
        std::string host;
        int         port           = 5432;
        int         max_connections = 10;
        int         timeout_ms     = 5000;
    };

    explicit Connection(Config cfg) : cfg_{std::move(cfg)} {}

    // ... connection methods ...

private:
    Config cfg_;
};

class ConnectionBuilder {
public:
    explicit ConnectionBuilder(std::string host)
        : cfg_{.host = std::move(host)} {}              // C++20 designated init

    ConnectionBuilder& port(int p)            { cfg_.port            = p; return *this; }
    ConnectionBuilder& max_connections(int n) { cfg_.max_connections = n; return *this; }
    ConnectionBuilder& timeout_ms(int ms)     { cfg_.timeout_ms      = ms; return *this; }

    // Validates all invariants before constructing              // C++11
    std::unique_ptr<Connection> build() {
        if (cfg_.host.empty())
            throw std::invalid_argument("host must not be empty");
        if (cfg_.port < 1 || cfg_.port > 65535)
            throw std::invalid_argument("port out of range [1, 65535]");
        if (cfg_.max_connections < 1 || cfg_.max_connections > 256)
            throw std::invalid_argument("max_connections out of range [1, 256]");
        return std::make_unique<Connection>(std::move(cfg_)); // C++14
    }

private:
    Connection::Config cfg_;
};

class DatabaseFactory {
public:
    // Returns a builder, not the final object
    static ConnectionBuilder create(std::string host) {
        return ConnectionBuilder{std::move(host)};
    }
};

// Fluent usage — invariants checked only at build()
auto conn = DatabaseFactory::create("localhost")
                .port(5432)
                .max_connections(10)
                .timeout_ms(2000)
                .build();

Returning a Builder from the factory keeps Connection's constructor private-by-convention (it accepts only the validated Config struct), prevents partially configured objects from escaping, and lets the call site read like configuration prose. The build() step is the single location where all invariants are enforced, simplifying testing of error paths.


Dependency Injection via Factory

Singletons and global registries couple production code to concrete implementations, making unit tests expensive. The dependency injection alternative passes factory interfaces as constructor arguments, so production and test code can supply different implementations without modifying the class under test.

cpp
#include <memory>       // C++11

// Interfaces
class DbConnection {
public:
    virtual ~DbConnection() = default;
    virtual void query(std::string_view sql) = 0;
};

class IDbFactory {
public:
    virtual ~IDbFactory() = default;
    virtual std::unique_ptr<DbConnection> create() = 0;    // C++11
};

class ILogger {
public:
    virtual ~ILogger() = default;
    virtual void log(std::string_view msg) = 0;
};

// Consumer accepts interfaces — no knowledge of concrete types
class RequestHandler {
public:
    RequestHandler(ILogger& log, IDbFactory& db_factory)
        : log_{log}, db_factory_{db_factory} {}

    void handle(std::string_view request) {
        log_.log("handling request");
        auto db = db_factory_.create();        // fresh connection per request
        db->query("SELECT 1");
        log_.log("done");
    }

private:
    ILogger&    log_;
    IDbFactory& db_factory_;
};

// Production implementation
class RealDbConnection : public DbConnection {
public:
    explicit RealDbConnection(std::string dsn) : dsn_{std::move(dsn)} {}
    void query(std::string_view sql) override { /* execute via libpq */ }
private:
    std::string dsn_;
};

class RealDbFactory : public IDbFactory {
public:
    explicit RealDbFactory(std::string dsn) : dsn_{std::move(dsn)} {}
    std::unique_ptr<DbConnection> create() override {
        return std::make_unique<RealDbConnection>(dsn_); // C++14
    }
private:
    std::string dsn_;
};

In a test harness, replace RealDbFactory with a MockDbFactory that returns a preconfigured stub without opening any network socket. The RequestHandler class is completely unaware of the substitution.


Testing with Mock Factories

Mock factories are the natural complement to dependency injection. There are two main approaches: a lightweight std::function factory and a full virtual-interface mock.

std::function factory — the simplest option when the factory creates a single product type. Inject the factory as a callable; in tests, pass a lambda returning a test double.

cpp
#include <functional>   // C++11
#include <memory>

// std::function as a factory type — injectable in tests        // C++11
using CompressorFactory = std::function<std::unique_ptr<Compressor>()>;

class ArchiveWriter {
public:
    explicit ArchiveWriter(CompressorFactory factory)
        : factory_{std::move(factory)} {}

    void write(const uint8_t* data, size_t len) {
        auto comp = factory_();
        comp->compress(data, len);
    }

private:
    CompressorFactory factory_;
};

// Production
ArchiveWriter prod_writer([] { return std::make_unique<ZstdCompressor>(); });

// Test — inject a fake that records calls
struct FakeCompressor : public Compressor {
    std::vector<uint8_t> compress(const uint8_t* data, size_t len) override {
        call_count++;
        return {};
    }
    int call_count = 0;
};

void test_archive_writer_calls_compressor() {
    FakeCompressor* fake = nullptr;
    ArchiveWriter writer([&fake] {
        auto c = std::make_unique<FakeCompressor>();
        fake = c.get();              // borrow raw pointer for inspection
        return c;
    });

    uint8_t buf[4] = {};
    writer.write(buf, sizeof(buf));
    assert(fake != nullptr && fake->call_count == 1);
}

Google Mock virtual factory — when the factory interface has multiple methods or you need call-count and argument matchers, derive a mock from the virtual interface:

cpp
#include <gmock/gmock.h>  // Google Mock

class MockDbFactory : public IDbFactory {
public:
    MOCK_METHOD(std::unique_ptr<DbConnection>, create, (), (override));
};

TEST(RequestHandlerTest, QueriesDatabase) {
    MockLogger         logger;
    MockDbFactory      db_factory;
    MockDbConnection*  conn_ptr = nullptr;

    // Return a mock connection on each call                     // C++11
    ON_CALL(db_factory, create()).WillByDefault([&conn_ptr] {
        auto conn = std::make_unique<MockDbConnection>();
        conn_ptr  = conn.get();
        return conn;
    });
    EXPECT_CALL(db_factory, create()).Times(1);

    RequestHandler handler{logger, db_factory};
    handler.handle("GET /");

    EXPECT_NE(conn_ptr, nullptr);
}

Prefer the std::function approach when the factory is a single-method interface — it avoids the GMock dependency and compiles faster. Switch to a virtual mock when you need EXPECT_CALL argument matchers or the factory interface has multiple methods that must be verified independently.


std::make_shared Workaround with Passkey

The earlier create() example used new DbConnection(...) wrapped in shared_ptr because std::make_shared cannot reach a private constructor. The separate allocation is a real cost: shared_ptr with new performs two heap allocations (one for the object, one for the control block), whereas make_shared fuses them into a single allocation.

The Passkey idiom recovers the single-allocation benefit while still preventing callers from constructing DbConnection directly. A private nested Passkey struct is declared inside DbConnection; the public constructor accepts it as its first argument. Only code that can name DbConnection::Passkey{} — i.e., DbConnection::create() itself — can construct the object.

cpp
#include <memory>       // C++11
#include <string>
#include <stdexcept>

class DbConnection {
public:
    // Passkey: public type, private default constructor          // C++11
    struct Passkey {
    private:
        Passkey() = default;                 // only DbConnection can construct it
        friend class DbConnection;
    };

    // Public constructor — but callers cannot form a Passkey{}
    DbConnection(Passkey, std::string_view dsn, int pool_size)
        : dsn_{std::string{dsn}}, pool_size_{pool_size} {}

    static std::shared_ptr<DbConnection>
    create(std::string_view dsn, int pool_size) {
        if (pool_size < 1 || pool_size > 128)
            throw std::invalid_argument("pool_size out of range [1, 128]");
        // Single allocation: Passkey{} is only reachable here   // C++11
        return std::make_shared<DbConnection>(Passkey{}, dsn, pool_size);
    }

private:
    std::string dsn_;
    int         pool_size_;
};

// Works — create() can construct Passkey{}
auto db = DbConnection::create("postgresql://localhost/mydb", 8);

// Compile error — Passkey() is private
// auto bad = std::make_shared<DbConnection>(DbConnection::Passkey{}, "...", 1);

The Passkey constructor is private with friend class DbConnection, so DbConnection::Passkey{} can only be constructed from within DbConnection's own member functions. External callers see the Passkey type but cannot instantiate it, making the public constructor effectively private while satisfying std::make_shared's requirement for a public constructor.


Best Practices

Return unique_ptr, not raw pointers. Raw pointer returns make ownership implicit and are indistinguishable from non-owning observers. unique_ptr conveys ownership at the type level and can be promoted to shared_ptr when needed.

Prefer factory functions over factory methods when the concrete type set is known and fixed. Virtual factory methods add a level of indirection that is only justified when the factory object itself must be swappable at runtime (i.e., the abstract factory scenario).

Use std::make_unique (C++14) rather than new inside factory bodies. In the presence of multiple function arguments that can throw, bare new can leak before the unique_ptr wraps it; make_unique avoids this.

Use strong types or enums for selector parameters in performance-sensitive factories. A std::string_view registry lookup is O(1) but involves hashing and comparison; an enum switch compiles to a jump table.

Validate in create(), not in the constructor. Factories are the natural place for precondition checks. Constructors that throw on bad arguments make two-phase construction patterns harder and leave partially initialized objects in play.

Prefer the Factory + Builder combination for objects with more than three optional parameters. Forwarding all parameters through a single create() call produces unreadable call sites and fragile positional argument ordering. A Builder's named setters are self-documenting and allow invariant validation to be deferred to build().

Inject factory interfaces rather than singletons in any code that is unit-tested. A singleton registry is a hidden global dependency; tests that create objects through singletons share state and are order-dependent. Accepting an IFactory& in the constructor makes the dependency explicit and swappable.


Common Pitfalls

Slicing through by-value returns. A factory for a polymorphic hierarchy that returns Base by value slices the derived part away. Always return through an owning pointer.

shared_ptr as the default return type. shared_ptr implies shared ownership and carries reference-counting overhead. Most factories should return unique_ptr and let callers decide whether to share.

Calling the registry before main(). The Meyers singleton ensures the registry object exists on first access, but registrar statics in other TUs may not have run yet if something invokes the factory during static initialization. Factories should not be called before main().

Linker stripping of self-registering TUs. In static libraries, TUs with no direct references from the application binary may be omitted entirely, removing all their registrations without any error. Test registration at startup or enforce linking with --whole-archive.

Forgetting = 0 on the virtual clone() declaration. If the base class's clone() is not pure virtual, derived classes that forget to override it silently inherit a base-class implementation that returns the wrong type (or fails to compile if the base class is abstract for other reasons). Make clone() pure virtual in the base.

Storing raw observer pointers into a PrototypeRegistry. The registry owns its prototypes via unique_ptr; returning raw Shape* from create() — instead of a clone — gives callers a non-owning pointer into the registry's storage. If the registry is destroyed or the prototype is replaced, all previously returned pointers dangle. Always return a fresh clone.


See Also

  • Virtual Constructor — clone() and create() as virtual methods on the base class
  • CRTP — compile-time polymorphism when the concrete type set is fixed at build time
  • Type Erasure — hiding concrete types without virtual dispatch overhead
  • RAII — pairing factory construction with deterministic destruction via unique_ptr deleters