Cpp Notes

ch9_patterns_and_idioms

Ch 9. Implementing Patterns and Idioms

Avoiding repetitive if-else statements in factory patterns

  • Problem with repetitive if...else in factory patterns

    • Leads to hard-to-maintain and error-prone code as conditions increase.
    • Common in object creation logic (e.g., image format handlers).
  • Initial implementation using if...else

    struct IImageFactory {
        virtual std::unique_ptr<Image> Create(std::string_view type) = 0;
    };
    
    struct ImageFactory : public IImageFactory {
        std::unique_ptr<Image> Create(std::string_view type) override {
            if (type == "bmp")
                return std::make_unique<BitmapImage>();
            else if (type == "png")
                return std::make_unique<PngImage>();
            else if (type == "jpg")
                return std::make_unique<JpgImage>();
            return nullptr;
        }
    };
  • Refactored implementation using a map of functions

    • Eliminates repetition by mapping format strings to factory lambdas.
    struct ImageFactory : public IImageFactory {
        std::unique_ptr<Image> Create(std::string_view type) override {
            static std::map<std::string, std::function<std::unique_ptr<Image>()>> mapping {
                { "bmp", []() { return std::make_unique<BitmapImage>(); } },
                { "png", []() { return std::make_unique<PngImage>(); } },
                { "jpg", []() { return std::make_unique<JpgImage>(); } }
            };
    
            auto it = mapping.find(type.data());
            if (it != mapping.end())
                return it->second();
            return nullptr;
        }
    };
  • How it works

    • The mapping associates a string key (image format) with a lambda that creates an image object.
    • Lambdas return std::unique_ptr<Image>, enabling polymorphism.
    • The factory method looks up the type and invokes the associated lambda.
  • Client usage remains unchanged

    auto factory = ImageFactory{};
    auto image = factory.Create("png");
  • Alternative using typeid instead of strings

    • Reduces string-based errors by using std::type_info.
    • Static mapping uses type_info* as key and lambdas as values.
    struct IImageFactoryByType {
        virtual std::unique_ptr<Image> Create(const std::type_info& type) = 0;
    };
    
    struct ImageFactoryByType : public IImageFactoryByType {
        std::unique_ptr<Image> Create(const std::type_info& type) override {
            auto it = mapping.find(&type);
            if (it != mapping.end())
                return it->second();
            return nullptr;
        }
    
    private:
        static std::map<const std::type_info*, std::function<std::unique_ptr<Image>()>> mapping;
    };
    
    std::map<const std::type_info*, std::function<std::unique_ptr<Image>()>> ImageFactoryByType::mapping {
        { &typeid(BitmapImage), []() { return std::make_unique<BitmapImage>(); } },
        { &typeid(PngImage),    []() { return std::make_unique<PngImage>(); } },
        { &typeid(JpgImage),    []() { return std::make_unique<JpgImage>(); } }
    };
  • Client usage with type-based factory

    auto factory = ImageFactoryByType{};
    auto image = factory.Create(typeid(PngImage));
  • Considerations

    • Using a map simplifies adding new image types.
    • Static map incurs a memory cost, relevant in constrained environments.
    • Type-based keys can be more reliable than string-based keys.

Great question — the use of a pointer to std::type_info (&typeid(...)) as the key in the std::map is a nuanced design choice. Let's break it down:


✅ Why use &typeid(T) instead of just typeid(T)?

Because std::type_info does not define a copyable or movable interface, and it lacks operator<, so it can't be directly used as a map key unless you use a custom comparator.

Using pointers to type_info (const std::type_info*) gets around that because:

  • Pointers are cheap to compare (just compare addresses),
  • Pointers have a well-defined operator<, so the map works out-of-the-box.

🧠 Is &typeid(T) stable and unique?

Yes — &typeid(T) returns a stable and unique address per type for the lifetime of the program. That makes it ideal as a key:

&typeid(BitmapImage) == &typeid(BitmapImage)  // always true

🧪 What's the danger of not using a pointer?

If you instead tried to store std::type_info directly:

std::map<std::type_info, std::function<...>> // ❌ won't compile

This fails because std::type_info:

  • Is not copyable (its copy constructor is deleted),
  • Doesn’t define a natural ordering (no < operator),
  • Cannot be value-inserted into std::map.

So, this is one of the rare use-cases where you must use a pointer, unless you go for a different container (e.g., unordered_map<std::type_index, T>

💡Alternative: Use std::type_index

If you want to avoid using raw pointers, there's a more modern and readable alternative:

#include <typeindex>

std::map<std::type_index, std::function<std::unique_ptr<Image>()>> mapping {
    { typeid(BitmapImage), []{ return std::make_unique<BitmapImage>(); } },
    { typeid(PngImage),    []{ return std::make_unique<PngImage>(); } },
    { typeid(JpgImage),    []{ return std::make_unique<JpgImage>(); } }
};

std::unique_ptr<Image> Create(const std::type_info& type) override {
    auto it = mapping.find(type);
    if (it != mapping.end()) return it->second();
    return nullptr;
}

🔁 std::type_index is a wrapper around type_info* that defines operator== and operator<, making it usable as a map key.

Implementing the pimpl idiom

  • Purpose of the pimpl idiom

    • Separates interface from implementation using an opaque pointer.
    • Minimizes recompilation when implementation details change.
    • Supports binary compatibility for ABI-stable libraries.
  • Initial control class (pre-refactoring)

    • Contains internal members:
      • std::string text, int width, int height, bool visible
    • Member functions:
      • draw(), set_text(), resize(), show(), hide()
    • Drawing simulates console output of properties.
  • Step-by-step refactoring using pimpl

    1. Move private members to separate class

      class control_pimpl;  // Forward declaration in control.h
    2. Use std::unique_ptr with custom deleter in control class

      class control {
          std::unique_ptr<control_pimpl, void(*)(control_pimpl*)> pimpl;
      public:
          control();
          void set_text(std::string_view);
          void resize(int, int);
          void show();
          void hide();
      };
    3. Define control_pimpl in control.cpp

      class control_pimpl {
          std::string text;
          int width = 0, height = 0;
          bool visible = true;
      
          void draw() {
              std::cout << "control\n"
                        << " visible: " << std::boolalpha << visible << std::noboolalpha << '\n'
                        << " size: " << width << ", " << height << '\n'
                        << " text: " << text << '\n';
          }
      
      public:
          void set_text(std::string_view t) {
              text = t.data();
              draw();
          }
          void resize(int w, int h) {
              width = w;
              height = h;
              draw();
          }
          void show() {
              visible = true;
              draw();
          }
          void hide() {
              visible = false;
              draw();
          }
      };
    4. Construct pimpl in control constructor

      control::control() :
          pimpl(new control_pimpl(), [](control_pimpl* p) { delete p; }) {}
    5. Redirect public methods to pimpl

      void control::set_text(std::string_view text) { pimpl->set_text(text); }
      void control::resize(int w, int h) { pimpl->resize(w, h); }
      void control::show() { pimpl->show(); }
      void control::hide() { pimpl->hide(); }
  • Why use custom deleter with unique_ptr

    • control_pimpl is incomplete in header at declaration site.
    • Custom deleter allows compilation without complete type.
    • Alternative: define destructor in .cpp after full type is visible.
  • Advantages

    • Interface and implementation are decoupled.
    • Changes in implementation don't require recompiling users.
    • Shorter build times due to reduced header dependencies.
    • Clean, stable headers for library consumers.
  • Disadvantages

    • Slight runtime overhead from indirection.
    • More boilerplate and harder to navigate.
    • Doesn't support private/protected or virtual members directly.
    • control_pimpl may need pointer back to control if bidirectional communication is required.
  • Copyable and movable variant (control_copyable)

    • Adds copy and move constructors/assignment operators.
    • Deep copies control_pimpl in copy operations.
    class control_copyable {
        std::unique_ptr<control_pimpl, void(*)(control_pimpl*)> pimpl;
    public:
        control_copyable();
        control_copyable(control_copyable&&) noexcept;
        control_copyable& operator=(control_copyable&&) noexcept;
        control_copyable(const control_copyable&);
        control_copyable& operator=(const control_copyable&);
    
        void set_text(std::string_view);
        void resize(int, int);
        void show();
        void hide();
    };
    
    control_copyable::control_copyable() :
        pimpl(new control_pimpl(), [](control_pimpl* p) { delete p; }) {}
    
    control_copyable::control_copyable(control_copyable&&) noexcept = default;
    control_copyable& control_copyable::operator=(control_copyable&&) noexcept = default;
    
    control_copyable::control_copyable(const control_copyable& op) :
        pimpl(new control_pimpl(*op.pimpl), [](control_pimpl* p) { delete p; }) {}
    
    control_copyable& control_copyable::operator=(const control_copyable& op) {
        if (this != &op) {
            pimpl = std::unique_ptr<control_pimpl, void(*)(control_pimpl*)>(
                new control_pimpl(*op.pimpl), [](control_pimpl* p) { delete p; });
        }
        return *this;
    }
  • General rule for pimpl

    • Move all private non-virtual data/functions to pimpl.
    • Leave virtual, protected, and base-related members in the public class.
  • Problem with positional parameters in C++

    • Arguments must be passed in order.
    • Cannot skip intermediate parameters when using default values.
    • Leads to unclear or error-prone code, especially with many optional parameters.
  • Named parameter idiom: emulating named arguments

    • Makes use of a helper class to encapsulate parameters.
    • Provides setter-style methods for optional parameters, allowing chained calls in any order.

Implementing the named parameter idiom

Original Class with Positional Parameters

class control {
    int id_;
    std::string text_;
    int width_;
    int height_;
    bool visible_;

public:
    control(
        int const id,
        std::string_view text = "",
        int const width = 0,
        int const height = 0,
        bool const visible = false
    ) : id_(id), text_(text), width_(width), height_(height), visible_(visible) {}
};

Refactored Using Named Parameter Idiom

  1. Parameter holder class

    class control_properties {
        int id_;
        std::string text_;
        int width_ = 0;
        int height_ = 0;
        bool visible_ = false;
    
        friend class control;
    
    public:
        control_properties(int const id) : id_(id) {}
    
        control_properties& text(std::string_view t) { text_ = t.data(); return *this; }
        control_properties& width(int const w) { width_ = w; return *this; }
        control_properties& height(int const h) { height_ = h; return *this; }
        control_properties& visible(bool const v) { visible_ = v; return *this; }
    };
  2. Constructor modified to accept property object

    class control {
        int id_;
        std::string text_;
        int width_;
        int height_;
        bool visible_;
    
    public:
        control(control_properties const& cp)
            : id_(cp.id_), text_(cp.text_),
              width_(cp.width_), height_(cp.height_), visible_(cp.visible_) {}
    };

Usage

  • Positional style (original):

    control c(1044, "sample", 100, 20, true);
  • Named parameter idiom:

    control c(control_properties(1044)
        .visible(true)
        .height(20)
        .width(100));

Notes

  • Required parameters are passed to the constructor of control_properties.
  • Optional parameters are set using member functions that:
    • Modify internal state.
    • Return a reference to enable chaining.
  • friend class control; allows direct access to internal members without public getters.
  • Enhances readability and flexibility when many optional parameters exist.

Trade-offs

Pros:

  • Improved clarity and flexibility.
  • Allows setting only needed parameters.
  • Enables setting in any order.
  • No need to modify the main class for minor parameter additions.

Cons:

  • More boilerplate code.
  • Additional class introduced.
  • friend usage limits encapsulation of control_properties.
  • Cannot be used directly with constructors needing many positional arguments.

Related idioms

  • Non-virtual interface idiom: separates public interface from implementation details.
  • Attorney-client idiom: restricts friend class access to selected private members.

Separating interfaces and implementations with the non-virtual interface idiom

  • Purpose of the Non-Virtual Interface (NVI) Idiom
    • Separates public interface from implementation details.
    • Prevents derived classes from overriding public behavior unexpectedly.
    • Uses public non-virtual methods and non-public virtual methods.

Design Guidelines (Herb Sutter)

  1. Public interfaces should be non-virtual.
  2. Virtual functions should be private.
  3. Use protected virtual functions only when base class implementations are intended to be called from derived classes.
  4. Destructor rules:
    • Public and virtual if deletion is polymorphic.
    • Protected and non-virtual if deletion is non-polymorphic.

❓ Can a derived class override a private virtual function?

Yes, absolutely.

C++ allows overriding private virtual functions from the base class — even though they are private — because:

  • Access control (like private, protected) is enforced during name lookup, not during overriding.
  • Virtual function overriding is based on signature matching (not visibility).

In other words: you can't call a private base class function, but you can override it.

🎯 The Key Insight: Overriding does not rely on name lookup.

It relies on matching the function signature during virtual table construction, not during normal name visibility resolution.

Let’s clarify: Name Lookup vs Overriding — Two Different Phases

Concept Description
Name Lookup Happens when the compiler tries to resolve a function call (like paint() in user code). Visibility (private, protected, public) matters here.
Overriding Happens during class definition — the compiler matches the function’s signature to a base class virtual function regardless of access control.
class Base {
private:
    virtual void foo();  // private virtual
};

class Derived : public Base {
    void foo() override;  // ✅ allowed
};
  • Base::foo() is private, so you can't call it from Derived.
  • But when the compiler sees Derived::foo(), it checks:
    • “Is there a virtual function in a base class with the same signature?”
    • Yes → so Derived::foo() overrides Base::foo() — even if Base::foo() is private.
  • This matching does not require name lookup — it’s part of the class layout logic.

🔁 Why doesn’t this break access control?

Because C++'s access control is meant to prevent certain code from using a member, not from defining an override.

Overriding is allowed because:

  • It doesn't call or access the base function.
  • It merely says, “When someone calls this virtual function through a base class reference/pointer, call my version.”

🚫 What the derived class cannot do:

class button : public control {
public:
    void foo() {
        paint();  // ❌ Error: 'paint' is private in 'control'
    }
};

That would result in a compiler error, because paint() is not accessible — even though it’s a virtual function.


Summary

Operation Allowed? Notes
Override private virtual ✅ Yes Signature must match
Call private base virtual ❌ No Unless from inside base class
Dispatch through base ✅ Yes Works as long as base calls the virtual
Access private base virtual ❌ No Visibility rules apply during lookup

Example: control Hierarchy with NVI Idiom

class control {
private:
    virtual void paint() = 0;  // Pure virtual, private

protected:
    virtual void erase_background() {
        std::cout << "erasing control background...\n";
    }

public:
    void draw() {
        erase_background();
        paint();  // Calls overridden paint() in derived class
    }

    virtual ~control() {}  // Virtual to allow safe polymorphic deletion
};

class button : public control {
private:
    void paint() override {
        std::cout << "painting button...\n";
    }

protected:
    void erase_background() override {
        control::erase_background();
        std::cout << "erasing button background...\n";
    }
};

class checkbox : public button {
private:
    void paint() override {
        std::cout << "painting checkbox...\n";
    }

protected:
    void erase_background() override {
        button::erase_background();
        std::cout << "erasing checkbox background...\n";
    }
};

How It Works

  • draw() is the only public method responsible for drawing.
  • paint() is private and must be implemented by derived classes.
  • erase_background() is protected and optionally called by derived classes.
  • This structure allows:
    • Enforced pre-/post-conditions in public methods.
    • Derived classes to specialize behavior without altering public API.
    • Clients to depend only on the guaranteed interface behavior.

Usage Example

std::vector<std::unique_ptr<control>> controls;
controls.emplace_back(std::make_unique<button>());
controls.emplace_back(std::make_unique<checkbox>());

for (auto& c : controls)
    c->draw();

Minimal Overhead Example

class control {
protected:
    virtual void initialize_impl() {
        std::cout << "initializing control...\n";
    }

public:
    void initialize() {
        initialize_impl();  // Public method calls protected virtual
    }
};

class button : public control {
protected:
    void initialize_impl() override {
        control::initialize_impl();
        std::cout << "initializing button...\n";
    }
};
  • Even though there's a level of indirection, compilers can optimize by inlining small functions.

Destructor Best Practices

Deleting a derived object through a base class pointer without a virtual destructor causes undefined behavior:

class Base {
    // ❌ Not virtual!
    ~Base() { std::cout << "Base dtor\n"; }
};

class Derived : public Base {
public:
    ~Derived() { std::cout << "Derived dtor\n"; }
};

Base* ptr = new Derived();
delete ptr;  // 💥 UB! Only Base's dtor gets called

Suggestions:

Design Pattern Destructor Declaration When to Use
🔁 Polymorphic base public: virtual ~Base(); Deleting via Base* is allowed/safe
🔒 Non-polymorphic base protected: ~Base(); Prevent deleting via base pointer
🔐 Final class (no inheritance) ~Class(); (default, non-virtual) No polymorphism; fast, compact

✅ Design Case 1: Public and virtual — for polymorphic use

class Base {
public:
    virtual ~Base() = default;
};

When to use:

  • You intend the class to be used polymorphically (e.g., with base pointers).
  • You want to allow users to write: Base* b = new Derived(); delete b;
  • Ensures the derived class destructor gets called properly through the vtable.
class Shape {
public:
    virtual void draw() = 0;
    virtual ~Shape() = default;  // ✅ allows deleting through Shape*
};

class Circle : public Shape {
public:
    ~Circle() { std::cout << "Circle dtor\n"; }
};

✅ Design Case 2: Protected and non-virtual — non-polymorphic, internal-use-only

class Base {
protected:
    ~Base() = default;
};

When to use:

  • The class is not meant to be used polymorphically (i.e., not to be deleted through Base*).
  • You don’t want clients to write delete basePtr; — you restrict that by making the destructor protected.
  • You don't need virtual dispatch because base destruction will always happen through Derived.
  • Prevents external deletion: delete basePtr; becomes a compile-time error.
  • Still allows Derived (which inherits from Base) to destroy the base part when deleted.

Example:

class RefCounted {
protected:
    ~RefCounted() = default;  // ❌ cannot be deleted externally
};

class Resource : public RefCounted {
public:
    ~Resource() = default;
};

// Later:
RefCounted* rc = new Resource();
// delete rc;  // ❌ Compile error: RefCounted::~RefCounted is protected

You’re saying: “Only I (or trusted code) should manage lifetime — not client code.”


Advantages of NVI Idiom

  • Clear separation between interface and implementation.
  • Enforces invariants and expected behavior.
  • Safer class extension and specialization.
  • Prevents public virtual methods from being misused by clients or mis-overridden in derived classes.

Handling friendship with the attorney-client idiom

  • Problem with traditional friendship in C++
    • Grants full access to all private and protected members.
    • Breaks encapsulation, even if the friend only needs limited access.
    • Creates tight coupling between classes.

Attorney-Client Idiom Overview

  • Introduces an intermediary class (the "Attorney") to control access.
  • The Client class makes only the Attorney its friend.
  • The Attorney class grants access to specific private members via static functions.
  • The actual friend accesses those specific parts through the attorney.

1. Initial Setup (Unrestricted Access)

class Client {
    int data_1;
    int data_2;
    void action1() {}
    void action2() {}

    friend class Friend;  // Full access (undesirable)
};

2. Use Attorney Class for Controlled Access

Client class only friends the attorney:
class Client {
    int data_1;
    int data_2;
    void action1() {}
    void action2() {}

    friend class Attorney;  // Restricted access via Attorney
};
Attorney class defines static accessors:
class Attorney {
    static inline void run_action1(Client& c) { c.action1(); }
    static inline int get_data1(Client& c) { return c.data_1; }

    friend class Friend;  // Only Friend can use these
};
Friend class uses attorney:
class Friend {
public:
    void access_client_data(Client& c) {
        Attorney::run_action1(c);
        int d1 = Attorney::get_data1(c);
        // No access to data_2 or action2()
    }
};

How It Works

  • Attorney is the only class that can access private members of Client.
  • Friend is only allowed to call what Attorney explicitly exposes.
  • Prevents misuse or accidental overreach into unrelated internals of Client.

Polymorphism Example

class B {
    virtual void execute() { std::cout << "base\n"; }
    friend class BAttorney;
};

class D : public B {
    void execute() override { std::cout << "derived\n"; }
};

class BAttorney {
    static inline void execute(B& b) { b.execute(); }
    friend class F;
};

class F {
public:
    void run() {
        B b;
        D d;
        BAttorney::execute(b); // prints: base
        BAttorney::execute(d); // prints: derived
    }
};
  • Friendship is not inherited, but virtual dispatch still works.
  • BAttorney::execute(d) invokes D::execute() polymorphically.

Benefits

  • Encapsulation is preserved.
  • Fine-grained access control to private members.
  • Can be tailored for different friends by creating multiple attorneys.
  • Helps build extensible, secure APIs or frameworks.

Trade-offs

  • More boilerplate code.
  • Increases maintenance complexity.
  • Can slow development/testing, especially in simpler applications.

When to Use

  • When needing to expose only a subset of internals.
  • For library/framework design where stable and restricted access is important.
  • When different friends require different levels of access to the same class.

When not to use

If your app logic or business code relies on attorney-style access to internals — that’s a red flag. It means your public interface may be insufficient, or you need better design patterns like:

  • Dependency injection
  • Visitor/Strategy patterns
  • Public façade over a private core

Static polymorphism with the curiously recurring template pattern

  • Static vs. Runtime Polymorphism
    • Runtime polymorphism (late binding): achieved via virtual functions; resolved at runtime.
    • Static polymorphism (early binding): resolved at compile-time using function templates, operator overloading, or CRTP.

Curiously Recurring Template Pattern (CRTP)

  • A derived class inherits from a base class template, with the derived class as the template parameter.
  • Enables compile-time polymorphism by letting the base class call functions from the derived class.
template <class T>
class control {
public:
    void draw() {
        static_cast<T*>(this)->erase_background();
        static_cast<T*>(this)->paint();
    }
};
class button : public control<button> {
public:
    void erase_background() {
        std::cout << "erasing button background...\n";
    }

    void paint() {
        std::cout << "painting button...\n";
    }
};

class checkbox : public control<checkbox> {
public:
    void erase_background() {
        std::cout << "erasing checkbox background...\n";
    }

    void paint() {
        std::cout << "painting checkbox...\n";
    }
};
template <class T>
void draw_control(control<T>& c) {
    c.draw();
}

button b;
draw_control(b);

checkbox c;
draw_control(c);
  • CRTP delays instantiation until the derived class is fully defined.
  • static_cast<T*>(this) safely downcasts to the derived type inside the template.
  • Calls like derived()->paint() are resolved at compile time.

Simplified CRTP with derived() helper

template <class T>
class control {
    T* derived() { return static_cast<T*>(this); }

public:
    void draw() {
        derived()->erase_background();
        derived()->paint();
    }
};

Common Pitfalls with CRTP

  • Access control: functions called from the base must be public or base must be declared a friend.

    class button : public control<button> {
    private:
        friend class control<button>;
        void erase_background() { ... }
        void paint() { ... }
    };
  • No homogeneous containers: each instantiation (e.g., control<button>, control<checkbox>) is a distinct type.


Workaround for Homogeneous Storage

1. Abstract base class

class controlbase {
public:
    virtual void draw() = 0;
    virtual ~controlbase() {}
};
template <class T>
class control : public controlbase {
public:
    void draw() override {
        static_cast<T*>(this)->erase_background();
        static_cast<T*>(this)->paint();
    }
};

// No changes to `button` and `checkbox` needed
std::vector<std::unique_ptr<controlbase>> controls;
controls.emplace_back(std::make_unique<button>());
controls.emplace_back(std::make_unique<checkbox>());

for (auto& c : controls)
    c->draw();

Advantages of CRTP

  • No runtime cost: everything is resolved at compile time.
  • Enables inline expansion of small functions.
  • Simulates virtual function behavior without actual virtual dispatch.

Trade-offs

  • Not intuitive at first glance.
  • Cannot use base class pointers/references directly for polymorphic behavior (without a common abstract base).
  • Template instantiation can increase binary size.

Adding functionality to classes with mixins

  • Purpose of Mixins in C++
    • Add reusable functionality to unrelated or already-defined classes.
    • Enable composition through inheritance by injecting behavior without modifying the original class.
    • Often confused with CRTP, but different in structure and direction of inheritance.

Key Difference: Mixin vs. CRTP

Pattern Structure Behavior
CRTP Derived : public Base<Derived> Base calls Derived’s functionality (compile-time polymorphism)
Mixin Mixin<T> : public T Mixin adds functionality to base (T), typically unrelated

Original classes with shared interface

class button {
public:
    void erase_background() {
        std::cout << "erasing button background...\n";
    }
    void paint() {
        std::cout << "painting button...\n";
    }
};

class checkbox {
public:
    void erase_background() {
        std::cout << "erasing checkbox background...\n";
    }
    void paint() {
        std::cout << "painting checkbox...\n";
    }
};

Mixin class adds new behavior

template <typename T>
class control : public T {
public:
    void draw() {
        T::erase_background();
        T::paint();
    }
};

so when you do control<button> - it creates something like

class control<button> : public button {
public:
    void draw() {
        button::erase_background();  // from button
        button::paint();             // from button
    }
};

Polymorphic Usage via Virtual Base

class control_base {
public:
    virtual ~control_base() {}
    virtual void draw() = 0;
};

Extend mixin to support runtime polymorphism

template <typename T>
class control : public control_base, public T {
public:
    void draw() override {
        T::erase_background();
        T::paint();
    }
};

Draw all controls

void draw_all(std::vector<control_base*> const& controls) {
    for (auto& c : controls) {
        c->draw();
    }
}

int main() {
    control<button> b;
    control<checkbox> c;

    std::vector<control_base*> controls = { &b, &c };
    draw_all(controls);
}

How It Works

  • control inherits from both control_base (for polymorphism) and T (to reuse functionality).
  • New behavior (draw()) is implemented using existing methods from T.
  • Compile-time reuse + runtime dispatch (if base interface is added).

When to Use Mixins

  • You want to extend functionality without altering the original class.
  • Multiple unrelated types need a shared behavior (e.g., logging, serialization, drawing).
  • You need reusable behavior blocks composed via inheritance.

Limitations

  • Not naturally polymorphic — need a virtual base like control_base.
  • Potential for diamond inheritance or ambiguity if multiple mixins are used carelessly.
  • Template code increases compilation time and binary size due to duplication.

Comparison Summary

Feature CRTP Mixin
Inheritance direction Derived : Base<Derived> Mixin<T> : T
Purpose Inject derived behavior into base Add common behavior to existing
Virtual dispatch needed No (static polymorphism) Optional (via virtual base)
Used for Compile-time customization, policy Functionality reuse, composition
Homogeneous containers Requires workaround (virtual base) Works with added virtual base

Expand a bit: mix in multiple base classes into a single control

using variadic templates and recursive inheritance

template <typename... Components>
class control;

We’ll recursively inherit from each component:

// Recursive class
template <typename Head, typename... Tail>
class control<Head, Tail...> : public Head, public control<Tail...> {
public:
    void draw() {
        Head::erase_background();
        Head::paint();
        control<Tail...>::draw();  // recurse
    }
};

// Base case (when there's only one type)
template <typename Last>
class control<Last> : public Last {
public:
    void draw() {
        Last::erase_background();
        Last::paint();
    }
};


// then you can call
control<button, checkbox> multi;
multi.draw();

Note, for control<Tail...>::draw(); - It's just calling the draw() function on the next "layer" of inheritance

  • namely, a control that wraps the remaining types in the variadic pack.

So:

control<T1, T2, T3>::draw() calls T1::methods() and then control<T2, T3>::draw()

⚠️ Caveats

  1. Diamond Inheritance?
    If two components inherit from the same base — beware. You may need virtual inheritance to avoid ambiguity.

  2. Name conflicts?
    If multiple components have the same method name but different semantics (e.g. both define reset()), you'll get ambiguity.

    You may resolve this with:

    using Head::erase_background;
    using Head::paint;

    Or disambiguate explicitly:

    Head::erase_background();
    control<Tail...>::draw();
  3. Complex construction
    If any component has non-default constructors, you’ll need to pass and forward parameters properly.


Handling unrelated types generically with the type erasure idiom

Why Do We Want Type Erasure?

Because C++'s regular polymorphism (via virtual functions) only works when:

  • All types inherit from a common base class (class hierarchy)
  • You own or can modify the types
  • You are okay with the tight coupling that comes with that hierarchy

But what if:

  • The types come from different libraries?
  • They don’t inherit a common base?
  • You want to handle lambdas, functors, pointers, objects uniformly?
  • You want to store different types in a single container?

🎯 Type erasure allows you to treat unrelated types polymorphically
without modifying them, without inheritance, and without knowing their type.

A Simple Analogy: std::function, you write:

std::function<int(int)> f;
f = [](int x) { return x * 2; };
f = std::bind(&SomeClass::method, obj, std::placeholders::_1);
f = my_callable_object;

All these types are different, but std::function erases their types and gives you a uniform way to call them.

You don’t care about the actual type — just that it’s callable with an int and returns an int.


Duck Typing vs. Type Erasure

  • Duck typing: If it walks like a duck… operate based on behavior, not type.

  • Type erasure: Wraps any type that satisfies a behavioral interface into a uniform type.

    • Removes (erases) concrete type info.
    • Enables runtime polymorphism for unrelated types.

Given types

class button {
public:
    void erase_background() { std::cout << "erasing button background...\n"; }
    void paint() { std::cout << "painting button...\n"; }
};

class checkbox {
public:
    void erase_background() { std::cout << "erasing checkbox background...\n"; }
    void paint() { std::cout << "painting checkbox...\n"; }
};

Type-erased wrapper: control

struct control {
    template <typename T>
    control(T&& obj)
        : ctrl(std::make_shared<control_model<T>>(std::forward<T>(obj))) {}

    void draw() { ctrl->draw(); }

    struct control_concept {
        virtual ~control_concept() = default;
        virtual void draw() = 0;
    };

    template <typename T>
    struct control_model : control_concept {
        control_model(T& unit) : t(unit) {}
        void draw() override {
            t.erase_background();
            t.paint();
        }
    private:
        T& t;
    };

private:
    std::shared_ptr<control_concept> ctrl;
};
int main() {
    checkbox cb;
    button btn;
    std::vector<control> controls = { control(cb), control(btn) };

    for (auto& c : controls)
        c.draw();
}

How it works

  • control_model<T> stores a reference to the object and forwards calls.
  • control_concept is the type-erased interface (pure virtual).
  • control hides implementation behind a shared pointer to control_concept.
  • Enables runtime polymorphism without requiring inheritance in the original types.

Alternative: Explicit Wrapper Hierarchy (More Verbose)

struct control_concept {
    virtual ~control_concept() = default;
    virtual void draw() = 0;
};

template <typename T>
struct control_wrapper : control_concept {
    control_wrapper(T& obj) : t(obj) {}
    void draw() override {
        t.erase_background();
        t.paint();
    }
private:
    T& t;
};

Usage

checkbox cb;
button btn;
control_wrapper<checkbox> cbw(cb);
control_wrapper<button> btnw(btn);
std::vector<control_concept*> v = { &cbw, &btnw };

for (auto& c : v)
    c->draw();
  • Redundant code avoided using a class template (control_wrapper).
  • Still requires manual management of pointers.

Encapsulating a Collection

struct control_collection {
    template <typename T>
    void add_control(T&& obj) {
        ctrls.push_back(std::make_shared<control_model<T>>(std::forward<T>(obj)));
    }

    void draw() {
        for (auto& c : ctrls)
            c->draw();
    }

    struct control_concept {
        virtual ~control_concept() = default;
        virtual void draw() = 0;
    };

    template <typename T>
    struct control_model : control_concept {
        control_model(T& unit) : t(unit) {}
        void draw() override {
            t.erase_background();
            t.paint();
        }
    private:
        T& t;
    };

private:
    std::vector<std::shared_ptr<control_concept>> ctrls;
};

Usage

int main() {
    checkbox cb;
    button btn;
    control_collection cc;

    cc.add_control(cb);
    cc.add_control(btn);
    cc.draw();
}

Advantages of Type Erasure

  • Enables runtime polymorphism for unrelated types.
  • Decouples interfaces from inheritance.
  • Type-safe alternative to void*.
  • Extensible without modifying original types.

C++ Standard Library Usage

  • std::function: wraps any callable type.
  • std::any: holds any copyable value.
  • std::shared_ptr: type-erased deleter.

Comparison with Other Idioms

Idiom Purpose Base Required Polymorphism Type
CRTP Compile-time polymorphism Yes Static
Mixins Extend functionality via templates No Static
Type Erasure Runtime polymorphism for unrelated types No Dynamic

Caution

  • Overhead from dynamic allocation (std::shared_ptr).
  • May increase code complexity.
  • If performance critical, prefer static polymorphism (CRTP).

Beautiful — you're asking the big-picture question now:

“What is type erasure really about? Why all these layers of abstraction?”

Let’s walk through the what, why, and how of type erasure — step by step — and then zoom in on the purpose behind each abstraction level.


🧠 What is Type Erasure?

Type erasure is a design technique where you:

Hide (or erase) the concrete type information of an object at compile time,
while still allowing certain operations to be performed on it at runtime.

Instead of saying:

“I know exactly what type this object is”
you say:
“I don’t care what type it is, as long as it has certain behavior.”


What's Actually Happening Internally?

1. The Concept (interface)

struct concept {
    virtual void do_something() = 0;
    virtual ~concept() = default;
};

This abstract base class serves as the runtime interface for the erased types.

It answers this question:

Once I've erased the type... how do I still know what I can do with it?

Since we erased the concrete type, we can’t access its real members anymore — we need a uniform interface to tell us what operations are allowed.

So concept is the “contract” — it says:

🗣️ “I don’t care what exact type you are. As long as you can implement do_something(), you can sit in this container and be operated on.”

🧱 Why not just store the object directly?

You can't — because in type erasure, you're intentionally hiding the object's type behind a pointer to a base class (concept* or smart pointer). This means:

  • You don't know the type at compile time anymore
  • So you can't directly call obj.do_something() unless you define what it means in some shared interface

Hence, the concept interface is what lets you interact with the object after the type has been erased.

This means:

  • We don’t need all types to share a common base class.
  • We just need them to support certain operations (like paint() and erase_background()).

The concept class is where we codify the duck:

“Anything that can do_something() is duck enough for me.”

👇 What happens without concept?

If you erase the type (say, using void* or std::any) without a virtual interface, then:

  • You can't call any method on the object
  • You must cast back to the original type — which defeats the whole purpose of type erasure
  • It becomes unsafe and unstructured

So concept gives you a safe, structured, extensible interface to interact with any wrapped type, without knowing its type.

E.g. It defines what all "ducks" must do, and the model<T> classes adapt specific types into that duck form.


2. The Model (wrapper)

template <typename T>
struct model : concept {
    T obj;
    model(T x) : obj(std::move(x)) {}
    void do_something() override {
        obj.do_something();  // or obj(), obj.paint(), etc.
    }
};

📌 This wraps the concrete type. It tells the concept:

"I know how to use this specific type to implement the expected behavior."

This is the glue between the erased interface and the real object.


3. The Erased Type (handle class)

class erased_type {
    std::unique_ptr<concept> ptr;
public:
    template <typename T>
    erased_type(T x) : ptr(std::make_unique<model<T>>(std::move(x))) {}

    void do_something() {
        ptr->do_something();
    }
};

📌 This is the unifying interface. It exposes only the abstract operations.
Clients don’t need to know (or care) what the real type is — they just use do_something().

Layers of abstractions

Layer Responsibility
Concept Defines the interface / expected behavior
Model Bridges concrete types to the concept
Handle Hides the details and provides a clean interface

You decouple the type from its behavior, and in return:

  • Get runtime polymorphism for arbitrary types
  • Maintain type safety (unlike void*)
  • Avoid inheritance requirements

Implementing a thread-safe singleton

  • Purpose of the Singleton Pattern
    • Restricts a class to a single instance.
    • Provides a global access point to that instance.
    • Commonly used for shared resources (e.g., config managers, loggers).
    • Thread-safety is essential when accessed from multiple threads.

Basic Singleton Implementation

class Singleton {
private:
    Singleton() = default;

public:
    Singleton(Singleton const&) = delete;
    Singleton& operator=(Singleton const&) = delete;

    static Singleton& instance() {
        static Singleton single; // Initialized once, thread-safe since C++11
        return single;
    }
};
  • Why it's thread-safe:
    • C++11 guarantees that static local variables are initialized exactly once, even in multithreaded contexts.
    • First call to instance() triggers initialization.
    • Subsequent calls return the already-initialized object.

Quote from C++ Standard (N4917, §6.7.5.2)

“If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.”

Why Return a Reference?

  • instance() returns a reference (Singleton&) because:
    • There’s no scenario where it would return null.
    • Prevents accidental deletion (as might be the case with a pointer).

Generic Singleton with CRTP

1. Singleton Base Template

template <class T>
class SingletonBase {
protected:
    SingletonBase() = default;

public:
    SingletonBase(SingletonBase const&) = delete;
    SingletonBase& operator=(SingletonBase const&) = delete;

    static T& instance() {
        static T single;
        return single;
    }
};


```cpp
class Single : public SingletonBase<Single> {
private:
    Single() = default;
    friend class SingletonBase<Single>;

public:
    void demo() {
        std::cout << "demo\n";
    }
};
int main() {
    Single::instance().demo();  // prints "demo"
}
  • CRTP Usage:
    • Avoids code duplication across multiple singleton types.
    • SingletonBase<T> manages the instantiation.
    • T (e.g., Single) provides the concrete type.

Design Considerations

  • Singleton usage can lead to hidden dependencies and global state, which may:
    • Complicate testing (no easy way to reset or replace instance).
    • Break encapsulation and violate SRP (Single Responsibility Principle).
  • Use when:
    • Only one instance is logically valid.
    • Shared, read-only access is required.

Alternatives to Singleton

  • Dependency Injection (pass shared instance explicitly).
  • Static class members.
  • Scoped or thread-local storage (when isolation is needed).