Flyweight in C++

Structural Hard

In one sentence: Flyweight uses sharing to support large numbers of fine-grained objects efficiently — store the context-independent part once, and pass the context-dependent part in.

Real-world analogy

A print shop with an order for 10,000 wedding invitations doesn't design 10,000 cards. It engraves one plate — the border, the couple's names, the date, the words "we would be delighted" — everything that is identical on every single card. Cutting that plate is slow and expensive. Stamping a card with it costs almost nothing, so the shop stamps 10,000.

The plate knows nothing about any particular invitation. It doesn't know which card in the stack it's pressing, who that card is for, or where in the pile it lands. All of that stays with the press operator, who feeds one card at a time and afterwards has the calligrapher write the guest's name in the blank space. The plate holds only what every impression shares; everything that varies is supplied at the moment of stamping.

When the next couple asks for the same gold-border design, the shop doesn't cut a second plate. It goes to the rack, finds the one already hanging there, and only engraves a new plate when a design genuinely doesn't exist yet. And if one couple wants their parents' invitation hand-lettered from scratch, the calligrapher draws that single card — beautiful, one of a kind, and it never goes on the rack.

Mapping the analogy to the pattern:

  • The engraved plate → a ConcreteFlyweight; what's cut into it is intrinsic state — shared precisely because it doesn't depend on which card is under the press.
  • The guest's name and which card in the stackextrinsic state, kept by the client and handed to the plate at stamping time.
  • The rack of plates → the FlyweightFactory: ask for "gold border" and you get the existing plate, or a freshly cut one if there isn't one yet — you never carve your own.
  • The press operator feeding cards → the Client, which stores or computes the varying part and passes it in.
  • The one hand-lettered invitation → an UnsharedConcreteFlyweight: the interface permits sharing, it doesn't require it.

Another angle: a cinema. One film print serves every screening; the hall, the showtime and the audience change from one showing to the next, and the reel is entirely indifferent to all three.

The problem

Your game renders a forest of 200,000 trees. Each Tree object stores its coordinates plus its species name, bark texture, and leaf mesh.

  1. The heavy data repeats. There are three species, but the texture bytes are copied 200,000 times.
  2. RAM runs out before the CPU does. The unique part (two integers) is a rounding error next to the megabytes duplicated behind it.
  3. Trimming fields doesn't help. The data is genuinely needed to draw a tree — it just doesn't need to exist 200,000 times.

GoF's example is a document editor that would love to model every character as an object — uniform treatment of text and embedded figures, easy support for new character sets — but a moderate document means hundreds of thousands of objects, and the naive version is simply too expensive to ship.

The solution — intrinsic vs. extrinsic state

This split is the pattern. Everything else is bookkeeping.

  • Intrinsic state is stored in the flyweight. It's information that is independent of the flyweight's context, which is exactly what makes it sharable. For a character glyph, that's the character code. For a tree, the species name and texture.
  • Extrinsic state depends on and varies with the context, so it cannot be shared. The client is responsible for storing or computing it and passing it to the flyweight whenever it calls an operation. For a character, that's its position on the page and its font. For a tree, its coordinates.

So the steps are:

  1. Find the state that repeats and doesn't depend on where the object is used — that's intrinsic; move it into a shared flyweight object.
  2. Everything left is extrinsic; keep it with the client and pass it in as a parameter.
  3. Add a factory that pools flyweights. Ask it for "Oak" and it returns the existing Oak, creating one only if none exists.

A shared flyweight has to behave like an independent object in every context it appears in — which is why it may never make assumptions about its context. Once you accept that rule, one object can stand in for a hundred thousand.

Note: GoF's implementation section says the pattern's applicability is decided almost entirely by how easy it is to remove the extrinsic state. Removing it wins you nothing if you end up with as many distinct pieces of extrinsic state as you had objects to begin with. The ideal case is extrinsic state that can be computed from a separate, much smaller structure rather than stored — GoF's editor keeps font runs in a small B-tree keyed by character index instead of a font pointer in every glyph. Two more practical notes: clients must never construct flyweights directly (they'd bypass sharing), and sharing normally implies reference counting — unless the flyweight set is small and fixed, like one per ASCII code, in which case you just keep them forever.

Structure

Structure of the Flyweight pattern Structure of the Flyweight pattern
Structure of the Flyweight pattern

  • Flyweight — declares the interface through which flyweights can receive and act on extrinsic state (note the parameter). In the example: TreeType.
  • ConcreteFlyweight — implements that interface and adds storage for intrinsic state. It must be sharable, so any state it holds has to be context-independent. In the example: TreeType again, since the example has only the shared kind.
  • UnsharedConcreteFlyweight — the Flyweight interface enables sharing but doesn't enforce it. It's common for unshared objects to sit at some level of the structure and have shared flyweights as children (GoF's Row and Column, whose children are shared Characters).
  • FlyweightFactory — creates and manages flyweights, and guarantees sharing: given a key, it hands back an existing instance or creates one if none exists. In the example: TreeTypeFactory.
  • Client — holds references to flyweights, and computes or stores their extrinsic state. In the example: Tree (position) and main.

C++ example

A forest where the factory proves it created only two heavy objects for five trees.

#include <iostream>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>

// Intrinsic state: heavy, immutable, shared by every tree of this species.
class TreeType {
public:
    TreeType(std::string name, std::string texture)
        : name_(std::move(name)), texture_(std::move(texture)) { ++created; }
    void draw(int x, int y) const {
        std::cout << name_ << " (" << texture_ << ") at " << x << "," << y << "\n";
    }
    static int created;
private:
    std::string name_;
    std::string texture_;
};
int TreeType::created = 0;

// The factory hands out shared flyweights instead of building new ones.
class TreeTypeFactory {
public:
    static std::shared_ptr<TreeType> get(const std::string& name,
                                         const std::string& texture) {
        auto& slot = cache_[name];
        if (!slot) slot = std::make_shared<TreeType>(name, texture);
        return slot;
    }
    static std::size_t distinctTypes() { return cache_.size(); }
private:
    static std::map<std::string, std::shared_ptr<TreeType>> cache_;
};
std::map<std::string, std::shared_ptr<TreeType>> TreeTypeFactory::cache_;

// Extrinsic state: tiny and unique per tree.
class Tree {
public:
    Tree(int x, int y, std::shared_ptr<TreeType> type)
        : x_(x), y_(y), type_(std::move(type)) {}
    void draw() const { type_->draw(x_, y_); }
private:
    int x_, y_;
    std::shared_ptr<TreeType> type_;
};

int main() {
    const std::string species[] = {"Oak", "Pine", "Oak", "Pine", "Oak"};

    std::vector<Tree> forest;
    for (int i = 0; i < 5; ++i)
        forest.emplace_back(i, i * 2,
                            TreeTypeFactory::get(species[i], species[i] + ".png"));

    for (const Tree& tree : forest) tree.draw();

    std::cout << "Trees: " << forest.size() << "\n";
    std::cout << "TreeType objects created: " << TreeType::created << "\n";
    std::cout << "Distinct types cached: " << TreeTypeFactory::distinctTypes() << "\n";
}
Output
Oak (Oak.png) at 0,0
Pine (Pine.png) at 1,2
Oak (Oak.png) at 2,4
Pine (Pine.png) at 3,6
Oak (Oak.png) at 4,8
Trees: 5
TreeType objects created: 2
Distinct types cached: 2

Five trees drew correctly, but the constructor counter says only two heavy objects were ever built — scale that to 200,000 trees and the saving is the whole point of the pattern. Notice that draw takes the position as a parameter: the coordinates are extrinsic, so the shared TreeType never learns where it is.

When should you use it?

GoF is unusually strict here: apply Flyweight when all of the following are true.

  • The application uses a large number of objects.
  • Storage costs are high purely because of that quantity.
  • Most object state can be made extrinsic — moved out of the object and passed in.
  • Once extrinsic state is removed, many groups of objects collapse into relatively few shared ones.
  • The application doesn't depend on object identity. This one bites: because flyweights are shared, an identity test will report "same object" for two conceptually distinct things.

Note: Flyweight is a memory optimization, and it costs you clarity and a pointer chase per access. Measure first. If you're creating a few thousand objects, the pattern is almost certainly not worth it.

Pros and cons

Pros (GoF's consequences)

  • Space savings that grow with sharing. The more flyweights are shared, the more you save, and the saving scales with how much intrinsic state each object had.
  • A double saving in the best case. When objects carry substantial intrinsic and extrinsic state, and the extrinsic part can be computed rather than stored, you save twice: sharing collapses the intrinsic state, and computation replaces the extrinsic storage. GoF's editor got 180,000 characters down to 480 objects in one real measurement.
  • Fine-grained objects become practical. Modelling every character or every tile as a real object stops being a fantasy, which unlocks uniform treatment and easier extension.
  • The pool also saves construction time for expensive resources like textures, and shared context-independent state is safe to read concurrently.

Cons (GoF's, plus the modern ones)

  • You trade space for time. Transferring, finding, or computing extrinsic state costs run-time — especially state that used to be a plain member and is now a lookup.
  • Identity is gone. Pointer comparison no longer means "the same logical object," so any code relying on identity has to be rewritten.
  • Combining with Composite has a sharp edge: shared leaf nodes cannot store a pointer to their parent, because they have many. The parent has to be passed down as extrinsic state, which changes how the whole hierarchy communicates.
  • Splitting state into intrinsic and extrinsic makes the code harder to follow, and flyweights must stay effectively immutable — one mutation silently corrupts every object sharing them.
  • The pool is global state, with the usual lifetime and thread-safety headaches; sharing implies reference counting unless the set is small and permanent.

How to remember it

Memory hook: "One printing plate, many names." Ask of every field: does this depend on where the object is used? If no, it's intrinsic and belongs in the shared flyweight; if yes, it's extrinsic and the caller passes it in. The sibling to keep straight is Singleton: Singleton enforces exactly one instance of a class, while Flyweight allows many shared instances — one per distinct intrinsic state — and it's a memory optimization, not an access-control rule.

  • Composite — the classic combination: a logically hierarchical structure implemented as a directed acyclic graph with shared leaf nodes. The price is the lost parent pointer described above.
  • State and Strategy — GoF says these are often best implemented as flyweights, since state and strategy objects usually hold behavior rather than context.
  • Singleton — enforces exactly one instance; Flyweight allows many shared instances, one per distinct intrinsic state.
  • Factory Method — the flyweight factory's get() is a factory operation that returns pooled objects instead of new ones.
  • Proxy — also stands between the client and an expensive object, but defers or controls access to one object rather than deduplicating many.