StructuralC++verifiedVerified
Flyweight Pattern in C++
Minimizes memory usage by sharing fine-grained objects that represent repeated data, storing intrinsic state once and passing extrinsic state at call time.
How to Implement the Flyweight Pattern in C++
1Step 1: Flyweight stores shared intrinsic state
class CharacterStyle {
std::string font_;
int size_;
std::string color_;
public:
CharacterStyle(std::string font, int size, std::string color)
: font_(std::move(font)), size_(size), color_(std::move(color)) {}
void render(char c, int x, int y) const {
std::cout << "'" << c << "' at (" << x << "," << y
<< ") font=" << font_ << " size=" << size_
<< " color=" << color_ << "\n";
}
};2Step 2: Factory ensures shared instances
class StyleFactory {
std::map<std::string, std::shared_ptr<CharacterStyle>> cache_;
public:
std::shared_ptr<CharacterStyle> getStyle(const std::string& font,
int size,
const std::string& color) {
auto key = font + ":" + std::to_string(size) + ":" + color;
auto it = cache_.find(key);
if (it != cache_.end()) return it->second;
auto style = std::make_shared<CharacterStyle>(font, size, color);
cache_[key] = style;
return style;
}
size_t uniqueStyles() const { return cache_.size(); }
};
int main() {
StyleFactory factory;
auto bold = factory.getStyle("Arial", 12, "black");
auto same = factory.getStyle("Arial", 12, "black"); // reuses existing
bold->render('H', 0, 0);
same->render('i', 10, 0);
std::cout << "Unique styles: " << factory.uniqueStyles() << "\n";
std::cout << "Same instance: " << (bold.get() == same.get() ? "yes" : "no") << "\n";
}#include <iostream>
#include <string>
#include <map>
#include <memory>
#include <vector>
#include <format>
#include <mutex>
#include <functional>
// [step] Immutable flyweight with weak_ptr cache for automatic cleanup
struct SpriteData {
const std::string texturePath;
const int width;
const int height;
const std::vector<uint8_t> pixelData; // shared bulk data
SpriteData(std::string path, int w, int h)
: texturePath(std::move(path)), width(w), height(h),
pixelData(w * h * 4, 128) {} // simulated pixel data
};
// [step] Thread-safe flyweight factory with weak references
class SpriteCache {
std::map<std::string, std::weak_ptr<const SpriteData>> cache_;
mutable std::mutex mu_;
size_t cacheHits_ = 0;
size_t cacheMisses_ = 0;
public:
std::shared_ptr<const SpriteData> get(const std::string& path,
int width, int height) {
std::lock_guard lk(mu_);
auto key = std::format("{}:{}x{}", path, width, height);
auto it = cache_.find(key);
if (it != cache_.end()) {
if (auto locked = it->second.lock()) {
++cacheHits_;
return locked;
}
cache_.erase(it);
}
++cacheMisses_;
auto sprite = std::make_shared<SpriteData>(path, width, height);
cache_[key] = sprite;
return sprite;
}
void cleanup() {
std::lock_guard lk(mu_);
std::erase_if(cache_, [](const auto& pair) {
return pair.second.expired();
});
}
struct Stats {
size_t activeEntries;
size_t hits;
size_t misses;
};
Stats stats() const {
std::lock_guard lk(mu_);
size_t active = 0;
for (const auto& [_, weak] : cache_)
if (!weak.expired()) ++active;
return {active, cacheHits_, cacheMisses_};
}
};
// [step] Extrinsic state held per instance
struct SpriteInstance {
std::shared_ptr<const SpriteData> data; // shared flyweight
float x, y; // unique per instance
float rotation;
float scale;
void render() const {
std::cout << std::format("Render '{}' at ({:.0f},{:.0f}) rot={:.0f} scale={:.1f}\n",
data->texturePath, x, y, rotation, scale);
}
size_t uniqueMemory() const {
return sizeof(SpriteInstance);
}
size_t sharedMemory() const {
return sizeof(SpriteData) + data->pixelData.size();
}
};
// [step] Demonstrate memory savings
int main() {
SpriteCache cache;
// Create 1000 tree sprites but only 3 unique textures
std::vector<SpriteInstance> forest;
forest.reserve(1000);
const char* trees[] = {"oak.png", "pine.png", "birch.png"};
for (int i = 0; i < 1000; ++i) {
forest.push_back({
cache.get(trees[i % 3], 64, 64),
static_cast<float>(i * 10),
static_cast<float>((i * 7) % 500),
static_cast<float>((i * 13) % 360),
0.8f + (i % 5) * 0.1f,
});
}
// Show first few
for (int i = 0; i < 3; ++i) forest[i].render();
// Memory analysis
size_t uniqueTotal = 0, sharedTotal = 0;
for (const auto& s : forest) {
uniqueTotal += s.uniqueMemory();
sharedTotal += s.sharedMemory();
}
auto stats = cache.stats();
std::cout << std::format(
"\nSprites: {}\n"
"Unique textures: {}\n"
"Cache hits: {}, misses: {}\n"
"Per-instance memory: {} bytes\n"
"Without flyweight: {} bytes\n"
"Savings: {:.1f}%\n",
forest.size(), stats.activeEntries,
stats.hits, stats.misses,
uniqueTotal,
uniqueTotal + sharedTotal,
100.0 * (1.0 - static_cast<double>(uniqueTotal + stats.activeEntries * forest[0].sharedMemory())
/ (uniqueTotal + sharedTotal)));
}Flyweight Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Flyweight Pattern in the Real World
“A book publisher doesn’t print a separate metal typeface block for every letter ‘e’ on every page. Instead, one block for ‘e’ (intrinsic state) is reused in every position, with the printer supplying the ink color and position (extrinsic state) each time it is stamped. Thousands of impressions share one piece of metal.”