Prototype
Learning Objectives
Create objects by cloning a prototype.
Handle shallow vs deep copies and ownership correctly.
Decide when cloning is clearer than factories.
Prerequisites: Module 12.
Intuition and Definition
Intent: specify the kinds of objects to create using a prototypical instance, and create new objects by copying it.
Problem: configuring an object is expensive or complex (loaded from a file, many settings), and you need many similar objects — or the concrete type is only known at run time through a base pointer.
Participants: Prototype interface with clone(), concrete prototypes, and often a prototype registry keyed by name.
Why It Matters
Prototype lets you copy an object without knowing its concrete class — the polymorphic copy C++ doesn't give you by default.
Syntax and Language Notes
import copy
template = load_vehicle_template("sedan")
car = copy.deepcopy(template) # deep copy: nested objects duplicated tooconst clone = structuredClone(original); // deep copy of plain data (no functions/classes)Complete Example
#include <iostream>
#include <map>
#include <memory>
#include <string>
#include <vector>
struct Warning { // Prototype interface
virtual ~Warning() = default;
virtual std::unique_ptr<Warning> clone() const = 0;
virtual std::string render() const = 0;
std::string text;
int priority = 0;
};
struct Telltale : Warning {
std::string icon;
std::unique_ptr<Warning> clone() const override { return std::make_unique<Telltale>(*this); } // copy ctor = deep enough here
std::string render() const override { return "[" + icon + "] " + text + " (p" + std::to_string(priority) + ")"; }
};
struct Chime : Warning {
std::vector<int> pattern; // owned data copied with the object
std::unique_ptr<Warning> clone() const override { return std::make_unique<Chime>(*this); }
std::string render() const override { return "chime x" + std::to_string(pattern.size()) + ": " + text; }
};
class WarningCatalog { // prototype registry
std::map<std::string, std::unique_ptr<Warning>> protos_;
public:
void add(const std::string& key, std::unique_ptr<Warning> p) { protos_[key]
…[OIL] Oil pressure low (p1)
[OIL] Oil pressure critically low (p0)
[OIL] Oil pressure low (p1) <- prototype unchanged
chime x3: Fasten seat beltStep-by-Step Walkthrough
The catalog stores fully configured prototypes; create clones one by key without knowing its concrete class.
Changing a clone (w2) doesn't affect the prototype or other clones — each owns its own copy, including Chime::pattern.
Because members are values and standard containers, the compiler-generated copy constructor is a correct deep copy (Rule of Zero). Raw owning pointers would need a hand-written deep copy.
Common Mistakes
Shallow clones of objects holding owning raw pointers or shared mutable state.
Copying identity — a cloned entity with the same database ID creates duplicates; reset IDs in clones of entities.
Cloning objects that hold resources (file handles, sockets) — usually should not be cloneable.
Trade-offs and Alternatives
Factories with parameters are clearer when configuration is cheap. Prototype shines when objects are pre-configured at run time (loaded from data files, designed in an editor) or when copying through a base pointer.
Real-World Use
Game engines spawn enemies from prefab prototypes; editors duplicate shapes; JavaScript's object model is literally prototype-based; configuration templates for vehicle variants.
Practice
Q1. (Easy) What method does every prototype provide?
Answer: clone().
Q2. (Medium) Why must a cloned *entity* usually get a new identity?
Answer: Two objects with the same ID would be treated as the same entity (overwrites, duplicate keys).
Q3. (Advanced) A prototype holds std::shared_ptr<Texture>. Should clone() deep-copy the texture?
Answer: Usually not — immutable, expensive shared resources (textures, fonts) are meant to be shared (Flyweight, Module 38). Clone the per-instance state and share the immutable resource; deep-copy only if clones are allowed to modify it.
Interview Question
Q1. "How do you copy an object through a base-class pointer in C++?"
Answer: With a virtual clone() that each concrete class implements as return std::make_unique<Derived>(*this); — the Prototype pattern. The copy constructor alone can't do it because constructors aren't virtual and copying through Base would slice. Covariant return types (Derived* clone()) work with raw pointers; with unique_ptr return unique_ptr<Base>.
Quick Quiz
Q1. What is a prototype registry?
Answer: A map from names to prototype objects that are cloned on demand.
Q2. Shallow or deep: which is safe for owning raw pointers?
Answer: Deep (or better, don't use owning raw pointers).
Q3. Which language is prototype-based at its core?
Answer: JavaScript.
Summary
Prototype creates objects by cloning configured instances, polymorphically.
Get copy depth and identity right.
Related: Modules 12, 38.