Factories in C++: Simple Factory vs Factory Method vs Abstract Factory, and Self-Registering Plugins
Key takeaways
A factory moves the choice of concrete type out of client code. Simple Factory centralizes it in one function, Factory Method lets subclasses decide, Abstract Factory swaps whole product families, and a registry lets new types plug in without editing the factory. Return std::unique_ptr, decide how unknown keys fail, and watch out for registrars that the linker never pulls in.
What problem a factory solves
When client code names concrete classes directly, every place that creates objects has to change whenever a new type appears:
// Client code that knows every concrete type
std::unique_ptr<Logger> logger;
if (config == "console") {
logger = std::make_unique<ConsoleLogger>();
} else if (config == "file") {
logger = std::make_unique<FileLogger>();
} else if (config == "network") {
logger = std::make_unique<NetworkLogger>();
}
If this block is copied into three places, adding SyslogLogger means finding all three. A factory moves the decision into one place so clients only depend on the Logger interface:
class LoggerFactory {
public:
static std::unique_ptr<Logger> create(const std::string& type) {
if (type == "console") return std::make_unique<ConsoleLogger>();
if (type == "file") return std::make_unique<FileLogger>();
if (type == "network") return std::make_unique<NetworkLogger>();
return nullptr;
}
};
auto logger = LoggerFactory::create(config);
That is the whole idea. The three classic variants below differ only in where the decision lives and what can be extended without editing existing code. It is worth being honest about the cost too: a factory adds an indirection that a reader has to follow, and a factory with exactly one product is usually just noise. The pattern pays off when the concrete type is chosen at run time (from config, a file, a network message) or when you want to keep heavy headers out of client translation units.
Simple Factory
#include <iostream>
#include <memory>
#include <string>
class Shape {
public:
virtual void draw() const = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
public:
void draw() const override { std::cout << "Drawing Circle\n"; }
};
class Rectangle : public Shape {
public:
void draw() const override { std::cout << "Drawing Rectangle\n"; }
};
class ShapeFactory {
public:
static std::unique_ptr<Shape> create(const std::string& type) {
if (type == "circle") return std::make_unique<Circle>();
if (type == "rectangle") return std::make_unique<Rectangle>();
return nullptr;
}
};
int main() {
auto shape = ShapeFactory::create("circle");
if (shape) {
shape->draw(); // Drawing Circle
}
}
Three details carry most of the weight here. The return type is std::unique_ptr<Shape>: ownership is transferred to the caller, and a caller that needs shared ownership can convert it with std::shared_ptr<Shape> s = ShapeFactory::create(...), which does not work in the other direction. The base class has a virtual destructor, without which destroying a Circle through a unique_ptr<Shape> is undefined behavior. And the unknown-key case returns nullptr, which is a decision you should make deliberately (see the pitfalls section).
The weakness of Simple Factory is that the factory has to include every concrete header and change for every new type. For a closed set of types that rarely changes, that is fine and it is the easiest version to read.
Factory Method
Factory Method turns the creation step into a virtual function. The base class contains the algorithm that uses the product; subclasses decide which product.
#include <iostream>
#include <memory>
class Document {
public:
virtual void open() = 0;
virtual ~Document() = default;
};
class PDFDocument : public Document {
public:
void open() override { std::cout << "Opening PDF\n"; }
};
class WordDocument : public Document {
public:
void open() override { std::cout << "Opening Word\n"; }
};
class Application {
public:
virtual ~Application() = default;
void newDocument() {
auto doc = createDocument(); // the factory method
doc->open();
}
protected:
virtual std::unique_ptr<Document> createDocument() = 0;
};
class PDFApplication : public Application {
protected:
std::unique_ptr<Document> createDocument() override {
return std::make_unique<PDFDocument>();
}
};
int main() {
std::unique_ptr<Application> app = std::make_unique<PDFApplication>();
app->newDocument(); // Opening PDF
}
Making createDocument protected is a small but useful choice: clients call newDocument(), and only the class hierarchy is allowed to create documents. The trade-off versus Simple Factory is that each new product tends to need a new subclass of the creator, so this fits frameworks where users are expected to subclass (application skeletons, test fixtures), and fits poorly when you only want to pick a type from a string.
Abstract Factory
Abstract Factory is a Factory Method for a family of products that must match each other. A Windows button next to a Mac checkbox is the classic example of the bug it prevents.
#include <iostream>
#include <memory>
class Button { public: virtual void render() = 0; virtual ~Button() = default; };
class Checkbox { public: virtual void render() = 0; virtual ~Checkbox() = default; };
class WindowsButton : public Button { public: void render() override { std::cout << "Windows Button\n"; } };
class WindowsCheckbox : public Checkbox { public: void render() override { std::cout << "Windows Checkbox\n"; } };
class MacButton : public Button { public: void render() override { std::cout << "Mac Button\n"; } };
class MacCheckbox : public Checkbox { public: void render() override { std::cout << "Mac Checkbox\n"; } };
class GUIFactory {
public:
virtual std::unique_ptr<Button> createButton() = 0;
virtual std::unique_ptr<Checkbox> createCheckbox() = 0;
virtual ~GUIFactory() = default;
};
class WindowsFactory : public GUIFactory {
public:
std::unique_ptr<Button> createButton() override { return std::make_unique<WindowsButton>(); }
std::unique_ptr<Checkbox> createCheckbox() override { return std::make_unique<WindowsCheckbox>(); }
};
class MacFactory : public GUIFactory {
public:
std::unique_ptr<Button> createButton() override { return std::make_unique<MacButton>(); }
std::unique_ptr<Checkbox> createCheckbox() override { return std::make_unique<MacCheckbox>(); }
};
int main() {
#ifdef _WIN32
std::unique_ptr<GUIFactory> factory = std::make_unique<WindowsFactory>();
#else
std::unique_ptr<GUIFactory> factory = std::make_unique<MacFactory>();
#endif
factory->createButton()->render();
factory->createCheckbox()->render();
}
The value is that the choice of family happens once, in one line, and the rest of the program cannot mix families by accident. The cost is rigidity along the other axis: adding a new product (say Slider) means adding a method to GUIFactory and to every concrete factory. If the product list changes more often than the family list, this pattern is the wrong shape. Also note that when the family is fixed at compile time, as with #ifdef _WIN32, a type alias or template parameter does the same job with no virtual calls; Abstract Factory earns its keep when the family is chosen at run time, such as a theme setting or a test double.
Self-registering factory
A registry replaces the if chain with a map from key to creator function. Each concrete type registers itself, so the factory never needs to include the concrete headers.
#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <string>
class Product {
public:
virtual void use() = 0;
virtual ~Product() = default;
};
class ProductFactory {
public:
using Creator = std::function<std::unique_ptr<Product>()>;
static void registerProduct(const std::string& type, Creator creator) {
registry()[type] = std::move(creator);
}
static std::unique_ptr<Product> create(const std::string& type) {
auto it = registry().find(type);
return it != registry().end() ? it->second() : nullptr;
}
private:
// Function-local static: constructed on first use, so registrars in
// other translation units can safely call registerProduct during
// static initialization.
static std::map<std::string, Creator>& registry() {
static std::map<std::string, Creator> reg;
return reg;
}
};
template <typename T>
struct AutoRegister {
explicit AutoRegister(const std::string& type) {
ProductFactory::registerProduct(type, [] { return std::make_unique<T>(); });
}
};
// In product_a.cpp
class ProductA : public Product {
public:
void use() override { std::cout << "Using Product A\n"; }
};
static AutoRegister<ProductA> registerA("A");
int main() {
if (auto product = ProductFactory::create("A")) {
product->use(); // Using Product A
}
}
The registry() function is not decoration. If the map were a plain static data member, a registrar in product_a.cpp could run before the map in factory.cpp is constructed, because C++ does not order dynamic initialization across translation units. Inserting into a not-yet-constructed std::map is undefined behavior and typically crashes before main is reached. The function-local static is constructed the first time registry() is called, whichever translation unit calls it first.
Common pitfalls and fixes
Registrars that never run in a static library
This is the failure I have seen most often with self-registering factories. Everything works when the plugin .cpp files are compiled straight into the executable. Then the plugins are moved into a static library, and the factory silently reports zero registered types. No error, no warning.
The reason is how static libraries are linked. A .a or .lib is an archive of object files, and the linker only pulls in an object file if it resolves a symbol that something else still needs. A file containing nothing but a class and a static AutoRegister<...> variable exports nothing that main references, so the linker skips it and its static constructor is never part of the program. With a small test program (registry in a header, image.cpp registering one plugin), linking the object directly printed registered: 1, while linking the same object through libplugins.a printed registered: 0.
The fixes, from least to most invasive:
- Force the whole archive in:
-Wl,--whole-archive -lplugins -Wl,--no-whole-archivewith GNU ld,/WHOLEARCHIVE:plugins.libwith MSVC,-force_loadwith Apple’s linker. In CMake, an object library (add_library(plugins OBJECT ...)) avoids the archive step entirely. - Reference each plugin from somewhere that is always linked, for example a
void registerAllPlugins()function that calls a registration function in each file. - Load real plugins as shared libraries with
dlopen/LoadLibrary; their static constructors run when the library is loaded.
Calling the factory method from the base constructor
The second classic mistake is calling the factory method from the creator’s constructor, so the product is ready as soon as the object exists. During the base-class constructor, the object is still a base-class object, so the virtual call does not reach the subclass override. If the method is pure virtual, GCC’s runtime aborts with:
pure virtual method called
terminate called without an active exception
Compilers usually warn when the pure virtual call is made directly in the constructor, but not when it is hidden behind another member function such as init(), which is exactly how it tends to appear in real code. Call the factory method lazily (on first use) or from a separate initialize() step after construction.
Unchecked nullptr for unknown keys
auto product = ProductFactory::create("unknown");
product->use(); // null dereference, usually a segfault
Returning nullptr pushes a check onto every caller, and some of them will forget. Decide on one policy: return nullptr and document it, throw std::invalid_argument with the key in the message, or return std::expected<std::unique_ptr<Product>, Error> in C++23. Keys usually come from configuration, so including the unknown key and the list of valid keys in the error message saves time.
Returning raw pointers
Product* create(const std::string& type) { return new ConcreteProduct(); } // who deletes this?
A raw owning pointer leaks on the first early return in the caller. Return std::unique_ptr; it costs nothing extra and callers that need shared_ptr can convert.
Registering while other threads create
Registration during static initialization happens before main starts other threads, so reading the map afterwards is safe without a lock. If types can be registered later (plugins loaded at run time), std::map needs a mutex around both registration and lookup, or a std::shared_mutex if lookups vastly outnumber registrations.
Production variations
Factories with constructor arguments
When products need parameters, either give each product its own named creation function or make the creator signature take a configuration object:
class FileLogger : public Logger {
public:
explicit FileLogger(std::string path) : path_(std::move(path)) {}
void log(const std::string& msg) override {
std::cout << "[File:" << path_ << "] " << msg << '\n';
}
private:
std::string path_;
};
using LoggerCreator = std::function<std::unique_ptr<Logger>(const LoggerConfig&)>;
Resist the temptation to pass a std::map<std::string, std::string> of options to every creator; it moves type errors from compile time to run time.
Factory as an object instead of statics
A factory held as an object (and passed into the code that needs it) is easier to test than a set of static functions, because a test can register fakes into its own instance:
class PluginFactory {
public:
using Creator = std::function<std::unique_ptr<Plugin>()>;
void registerCreator(const std::string& name, Creator c) { creators_[name] = std::move(c); }
std::unique_ptr<Plugin> create(const std::string& name) const {
auto it = creators_.find(name);
return it != creators_.end() ? it->second() : nullptr;
}
private:
std::map<std::string, Creator> creators_;
};
A global instance() accessor on top of this is convenient for self-registration, but keep the class itself usable as a normal object.
Full example: plugin system
#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <string>
class Plugin {
public:
virtual void execute() = 0;
virtual std::string getName() const = 0;
virtual ~Plugin() = default;
};
class PluginFactory {
public:
using Creator = std::function<std::unique_ptr<Plugin>()>;
static PluginFactory& instance() {
static PluginFactory inst;
return inst;
}
void registerPlugin(const std::string& name, Creator creator) {
creators_[name] = std::move(creator);
}
std::unique_ptr<Plugin> create(const std::string& name) const {
auto it = creators_.find(name);
if (it != creators_.end()) {
return it->second();
}
std::cerr << "Plugin not found: " << name << '\n';
return nullptr;
}
void listPlugins() const {
std::cout << "Available plugins:\n";
for (const auto& [name, creator] : creators_) {
std::cout << " - " << name << '\n';
}
}
private:
PluginFactory() = default;
std::map<std::string, Creator> creators_;
};
template <typename T>
struct PluginRegistrar {
explicit PluginRegistrar(const std::string& name) {
PluginFactory::instance().registerPlugin(name, [] { return std::make_unique<T>(); });
}
};
class ImagePlugin : public Plugin {
public:
void execute() override { std::cout << "Processing image...\n"; }
std::string getName() const override { return "ImagePlugin"; }
};
class VideoPlugin : public Plugin {
public:
void execute() override { std::cout << "Processing video...\n"; }
std::string getName() const override { return "VideoPlugin"; }
};
static PluginRegistrar<ImagePlugin> registerImage("image");
static PluginRegistrar<VideoPlugin> registerVideo("video");
int main() {
PluginFactory::instance().listPlugins();
if (auto plugin = PluginFactory::instance().create("image")) {
std::cout << "Loaded: " << plugin->getName() << '\n';
plugin->execute();
}
}
Output (keys come out sorted because std::map is ordered):
Available plugins:
- image
- video
Loaded: ImagePlugin
Processing image...
In a real project each plugin and its registrar would live in its own .cpp file, which is exactly the layout that triggers the static-library problem above, so decide how the plugin files are linked at the same time you introduce the registry.
Choosing between the variants
| Variant | Where the decision lives | Easy to add | Hard to add |
|---|---|---|---|
| Simple Factory | One function | Nothing without editing it | Any new type |
| Factory Method | A virtual function in subclasses | New creator subclasses | Changing the product interface |
| Abstract Factory | One concrete factory per family | New families | New products in the family |
| Registry | A map filled by registrars | New types, even from other modules | Compile-time checking of keys |
If the set of types is closed and known at compile time, std::variant plus std::visit or a template may replace the whole hierarchy. Factories are the right tool when the type genuinely has to be chosen at run time.
Related Articles
- Design Patterns in Modern C++: What Singleton, Factory, Observer, Strategy and Visitor Look Like After C++17
- C++ Observer pattern
- C++ Strategy pattern
- C++ Decorator pattern
- CRTP vs Virtual Functions: Static Polymorphism Without vtable Overhead
- C++ virtual functions
- C++ Smart Pointers
- C++ Static Initialization Order: Causes and Fixes