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 too
const 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 belt

Step-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.