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++11A 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.
// 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++11Factory 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.
#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.
#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:
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:
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.
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.
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.
See Also
- Virtual Constructor —
clone()andcreate()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_ptrdeleters