본문으로 건너뛰기
Previous
Next
C++ Type Erasure Complete Guide

C++ Type Erasure Complete Guide

C++ Type Erasure Complete Guide

이 글의 핵심

Master C++ Type Erasure: hide concrete types behind stable interfaces. Complete guide to std::function, std::any, manual models, tradeoffs, and practical applications.

What is Type Erasure?

Type Erasure hides type information and handles various types through a unified interface. This design pattern enables polymorphism without requiring inheritance. std::any and std::function are prominent examples.

#include <any>
#include <iostream>
using namespace std;
int main() {
    any a = 10;
    cout << any_cast<int>(a) << endl;  // 10
    
    a = 3.14;
    cout << any_cast<double>(a) << endl;  // 3.14
    
    a = string("Hello");
    cout << any_cast<string>(a) << endl;  // Hello
}

Why is it needed?

  • No inheritance required: Implements polymorphism without modifying existing types.
  • Flexibility: Useful when types are unknown at compile time.
  • Decoupling: Removes dependencies between types.
  • Reusability: Provides consistent interface for diverse types.
// ❌ Virtual functions: requires inheritance
class Shape {
public:
    virtual void draw() = 0;
};
class Circle : public Shape {  // Requires inheritance
    void draw() override {}
};
// ✅ Type Erasure: no inheritance required
class Drawable {
    // Internal implementation...
};
class Circle {  // No inheritance needed!
    void draw() {}
};
Drawable d = Circle();  // OK
d.draw();

Type Erasure Structure

graph TD
    A[Drawable External Interface] --> B[DrawableConcept Abstract Interface]
    B --> C[DrawableModel<Circle>]
    B --> D[DrawableModel<Rectangle>]
    C --> E[Circle Object]
    D --> F[Rectangle Object]
    
    style A fill:#90EE90
    style B fill:#FFB6C1
    style C fill:#87CEEB
    style D fill:#87CEEB

Type Erasure vs Virtual Functions

FeatureVirtual FunctionsType Erasure
Inheritance✅ Required❌ Not required
Modify existing type✅ Required❌ Not required
Flexibility❌ Limited✅ High
Implementation Complexity✅ Low❌ High
Performance✅ Fast❌ Slightly slower
Type Information✅ Maintained❌ Erased
// Virtual functions: requires inheritance
class Shape {
public:
    virtual void draw() = 0;
};
class Circle : public Shape {
    void draw() override {
        std::cout << "Circle\n";
    }
};
// Type Erasure: no inheritance required
class Circle {  // Does not inherit Shape
public:
    void draw() const {
        std::cout << "Circle\n";
    }
};
Drawable d = Circle();  // OK

Table of Contents

  1. std::function: Function Type Erasure
  2. std::any: Type Erasure for Arbitrary Types
  3. Manual Type Erasure Implementation
  4. Practical Use Cases
  5. Common Issues
  6. Comparison Table
  7. Practical Patterns

1. std::function: Function Type Erasure

What is std::function?

std::function is type-erased function wrapper that stores any callable object (function pointer, lambda, functor, etc.) with matching signature.

#include <functional>
#include <iostream>
void freeFunc() { std::cout << "Free function\n"; }
struct Functor {
    void operator()() const { std::cout << "Functor\n"; }
};
int main() {
    std::function<void()> f;
    f = freeFunc;
    f();  // Free function
    f = Functor{};
    f();  // Functor
    f = []() { std::cout << "Lambda\n"; };
    f();  // Lambda
    return 0;
}

Practical Example: Callback System

#include <functional>
#include <vector>
#include <iostream>
class EventManager {
    std::vector<std::function<void(int)>> listeners;
public:
    void subscribe(std::function<void(int)> callback) {
        listeners.push_back(std::move(callback));
    }
    void emit(int value) {
        for (auto& listener : listeners) {
            listener(value);
        }
    }
};
int main() {
    EventManager em;
    em.subscribe([](int x) { std::cout << "Listener 1: " << x << "\n"; });
    em.subscribe([](int x) { std::cout << "Listener 2: " << x * 2 << "\n"; });
    em.emit(10);  // Both listeners called
    return 0;
}

Output:

Listener 1: 10
Listener 2: 20

std::function Characteristics

  • Flexible: Stores any callable with matching signature.
  • Cost: Heap allocation, indirect call overhead.
  • Copyable: Can copy std::function, but captured objects are copied too (expensive for large captures).

2. std::any: Type Erasure for Arbitrary Types

What is std::any?

std::any stores any type object, retrievable with any_cast. Type information checked at runtime.

#include <any>
#include <iostream>
#include <string>
int main() {
    std::any a = 10;
    std::cout << std::any_cast<int>(a) << "\n";  // 10
    a = 3.14;
    std::cout << std::any_cast<double>(a) << "\n";  // 3.14
    a = std::string("Hello");
    std::cout << std::any_cast<std::string>(a) << "\n";  // Hello
    return 0;
}

Practical Example: Type-Safe Any Container

#include <any>
#include <map>
#include <string>
#include <iostream>
class Config {
    std::map<std::string, std::any> data;
public:
    template <typename T>
    void set(const std::string& key, T value) {
        data[key] = std::move(value);
    }
    template <typename T>
    T get(const std::string& key) const {
        return std::any_cast<T>(data.at(key));
    }
};
int main() {
    Config cfg;
    cfg.set("port", 8080);
    cfg.set("host", std::string("localhost"));
    cfg.set("timeout", 30.0);
    std::cout << "Port: " << cfg.get<int>("port") << "\n";
    std::cout << "Host: " << cfg.get<std::string>("host") << "\n";
    std::cout << "Timeout: " << cfg.get<double>("timeout") << "\n";
    return 0;
}

std::any Characteristics

  • Flexibility: Stores arbitrary types.
  • Runtime check: any_cast throws std::bad_any_cast if type mismatches.
  • Cost: Heap allocation for large types, type info check overhead.

3. Manual Type Erasure Implementation

Basic Structure

Manual type erasure uses Concept (abstract interface) and Model (concrete implementation) pattern.

#include <memory>
#include <iostream>
class Drawable {
    struct Concept {
        virtual ~Concept() = default;
        virtual void draw() const = 0;
    };
    template <typename T>
    struct Model : Concept {
        T object;
        Model(T obj) : object(std::move(obj)) {}
        void draw() const override {
            object.draw();
        }
    };
    std::unique_ptr<Concept> pImpl;
public:
    template <typename T>
    Drawable(T obj) : pImpl(std::make_unique<Model<T>>(std::move(obj))) {}
    void draw() const {
        pImpl->draw();
    }
};
// No inheritance needed!
struct Circle {
    void draw() const { std::cout << "Circle\n"; }
};
struct Rectangle {
    void draw() const { std::cout << "Rectangle\n"; }
};
int main() {
    Drawable d1 = Circle{};
    Drawable d2 = Rectangle{};
    d1.draw();  // Circle
    d2.draw();  // Rectangle
    return 0;
}

Structure explanation:

  • Concept: Abstract interface defining draw() virtual function.
  • Model<T>: Template class inheriting Concept, holding concrete type T object.
  • Drawable: External interface holding unique_ptr<Concept>, hiding concrete type.

Detailed Implementation: Copyable Type Erasure

#include <memory>
#include <iostream>
class Drawable {
    struct Concept {
        virtual ~Concept() = default;
        virtual void draw() const = 0;
        virtual std::unique_ptr<Concept> clone() const = 0;
    };
    template <typename T>
    struct Model : Concept {
        T object;
        Model(T obj) : object(std::move(obj)) {}
        void draw() const override {
            object.draw();
        }
        std::unique_ptr<Concept> clone() const override {
            return std::make_unique<Model<T>>(object);
        }
    };
    std::unique_ptr<Concept> pImpl;
public:
    template <typename T>
    Drawable(T obj) : pImpl(std::make_unique<Model<T>>(std::move(obj))) {}
    // Copy constructor
    Drawable(const Drawable& other)
        : pImpl(other.pImpl ? other.pImpl->clone() : nullptr) {}
    // Copy assignment
    Drawable& operator=(const Drawable& other) {
        if (this != &other) {
            pImpl = other.pImpl ? other.pImpl->clone() : nullptr;
        }
        return *this;
    }
    // Move constructor/assignment = default
    Drawable(Drawable&&) = default;
    Drawable& operator=(Drawable&&) = default;
    void draw() const {
        if (pImpl) pImpl->draw();
    }
};

Key: clone() virtual function enables copying Drawable itself. Each Model<T> implements clone() returning newly allocated Model<T> copy.

4. Practical Use Cases

4.1 Callback System

#include <functional>
#include <vector>
class EventSystem {
    std::vector<std::function<void(int)>> callbacks;
public:
    void subscribe(std::function<void(int)> cb) {
        callbacks.push_back(std::move(cb));
    }
    void trigger(int value) {
        for (auto& cb : callbacks) {
            cb(value);
        }
    }
};

4.2 Type-Safe Any

#include <any>
#include <map>
#include <string>
class PropertyBag {
    std::map<std::string, std::any> props;
public:
    template <typename T>
    void set(const std::string& key, T value) {
        props[key] = std::move(value);
    }
    template <typename T>
    T get(const std::string& key) const {
        return std::any_cast<T>(props.at(key));
    }
};

4.3 Plugin System

class Plugin {
    struct Concept {
        virtual ~Concept() = default;
        virtual void execute() = 0;
    };
    template <typename T>
    struct Model : Concept {
        T plugin;
        Model(T p) : plugin(std::move(p)) {}
        void execute() override {
            plugin.execute();
        }
    };
    std::unique_ptr<Concept> pImpl;
public:
    template <typename T>
    Plugin(T p) : pImpl(std::make_unique<Model<T>>(std::move(p))) {}
    void execute() {
        pImpl->execute();
    }
};
// Plugin implementations (no inheritance needed)
struct LogPlugin {
    void execute() { std::cout << "Logging...\n"; }
};
struct CachePlugin {
    void execute() { std::cout << "Caching...\n"; }
};
int main() {
    std::vector<Plugin> plugins;
    plugins.emplace_back(LogPlugin{});
    plugins.emplace_back(CachePlugin{});
    for (auto& p : plugins) {
        p.execute();
    }
    return 0;
}

5. Common Issues

Issue 1: any_cast Failure

#include <any>
#include <iostream>
int main() {
    std::any a = 10;
    try {
        double d = std::any_cast<double>(a);  // ❌ Type mismatch
    } catch (const std::bad_any_cast& e) {
        std::cout << "Error: " << e.what() << "\n";
    }
    // ✅ Correct type
    int i = std::any_cast<int>(a);
    std::cout << i << "\n";  // 10
    return 0;
}

Solution: Use any_cast<T*> to check type before casting.

if (auto* ptr = std::any_cast<int>(&a)) {
    std::cout << *ptr << "\n";
} else {
    std::cout << "Not an int\n";
}

Issue 2: std::function Copy Cost

// ❌ Bad: copying large captures
std::vector<int> largeData(1000000);
std::function<void()> f = [largeData]() {  // Copies largeData
    // Use largeData...
};
std::function<void()> f2 = f;  // Copies largeData again!

Solution: Use std::move or capture by reference.

// ✅ Move
std::function<void()> f = [data = std::move(largeData)]() {
    // Use data...
};
// ✅ Reference (caution: lifetime management needed)
std::function<void()> f = [&largeData]() {
    // Use largeData...
};

Issue 3: Type Info Loss

std::any a = std::string("Hello");
// ❌ Can't directly call string methods
// a.size();  // Error
// ✅ Must cast first
std::string& s = std::any_cast<std::string&>(a);
std::cout << s.size() << "\n";  // 5

Issue 4: Performance Overhead

Type erasure involves heap allocation and indirect calls. For hot paths, consider templates directly.

// ❌ Slow: type erasure overhead
void processSlow(std::function<int(int)> f) {
    for (int i = 0; i < 1000000; ++i) {
        f(i);  // Indirect call
    }
}
// ✅ Fast: template inline
template <typename F>
void processFast(F f) {
    for (int i = 0; i < 1000000; ++i) {
        f(i);  // Inlined
    }
}

6. Comparison Table

Type Erasure vs Other Polymorphism Techniques

FeatureVirtual FunctionsTemplatesType Erasure
Compile-time polymorphism
Runtime polymorphism
Inheritance required
Type info maintained
PerformanceMediumFastMedium
Binary sizeSmallLarge (template bloat)Medium
FlexibilityLowHighHigh

std::any vs std::variant vs std::optional

Featurestd::anystd::variantstd::optional
Stored typesArbitraryFixed setSingle type
Type checkRuntimeCompile-timeN/A
PerformanceSlowFastFast
SafetyRuntime exceptionCompile-timeCompile-time
Use caseUnknown typesKnown alternativesMay/may not exist

7. Practical Patterns

Pattern 1: Message Queue

#include <functional>
#include <queue>
#include <iostream>
class MessageQueue {
    std::queue<std::function<void()>> messages;
public:
    template <typename F>
    void post(F&& f) {
        messages.emplace(std::forward<F>(f));
    }
    void process() {
        while (!messages.empty()) {
            auto msg = std::move(messages.front());
            messages.pop();
            msg();
        }
    }
};
int main() {
    MessageQueue mq;
    mq.post([]() { std::cout << "Message 1\n"; });
    mq.post([]() { std::cout << "Message 2\n"; });
    mq.process();
    return 0;
}

Pattern 2: Command Pattern

#include <memory>
#include <vector>
#include <iostream>
class Command {
    struct Concept {
        virtual ~Concept() = default;
        virtual void execute() = 0;
    };
    template <typename T>
    struct Model : Concept {
        T cmd;
        Model(T c) : cmd(std::move(c)) {}
        void execute() override {
            cmd.execute();
        }
    };
    std::unique_ptr<Concept> pImpl;
public:
    template <typename T>
    Command(T cmd) : pImpl(std::make_unique<Model<T>>(std::move(cmd))) {}
    void execute() {
        pImpl->execute();
    }
};
struct PrintCommand {
    std::string msg;
    void execute() { std::cout << msg << "\n"; }
};
struct IncrementCommand {
    int& counter;
    void execute() { ++counter; }
};
int main() {
    int count = 0;
    std::vector<Command> commands;
    commands.emplace_back(PrintCommand{"Hello"});
    commands.emplace_back(IncrementCommand{count});
    commands.emplace_back(PrintCommand{"World"});
    for (auto& cmd : commands) {
        cmd.execute();
    }
    std::cout << "Counter: " << count << "\n";  // 1
    return 0;
}

Pattern 3: Function Wrapper (std::function Alternative)

#include <memory>
#include <iostream>
template <typename Signature>
class Function;
template <typename R, typename....Args>
class Function<R(Args...)> {
    struct Concept {
        virtual ~Concept() = default;
        virtual R invoke(Args....args) = 0;
    };
    template <typename F>
    struct Model : Concept {
        F func;
        Model(F f) : func(std::move(f)) {}
        R invoke(Args....args) override {
            return func(std::forward<Args>(args)...);
        }
    };
    std::unique_ptr<Concept> pImpl;
public:
    template <typename F>
    Function(F f) : pImpl(std::make_unique<Model<F>>(std::move(f))) {}
    R operator()(Args....args) {
        return pImpl->invoke(std::forward<Args>(args)...);
    }
};
int main() {
    Function<int(int, int)> add = [](int a, int b) { return a + b; };
    std::cout << add(3, 4) << "\n";  // 7
    Function<void()> greet = []() { std::cout << "Hello!\n"; };
    greet();  // Hello!
    return 0;
}

Pattern 4: Iterator Type Erasure

#include <memory>
#include <vector>
#include <list>
#include <iostream>
template <typename T>
class AnyIterator {
    struct Concept {
        virtual ~Concept() = default;
        virtual T& deref() = 0;
        virtual void next() = 0;
        virtual bool equal(const Concept& other) const = 0;
        virtual std::unique_ptr<Concept> clone() const = 0;
    };
    template <typename Iter>
    struct Model : Concept {
        Iter iter;
        Model(Iter it) : iter(it) {}
        T& deref() override { return *iter; }
        void next() override { ++iter; }
        bool equal(const Concept& other) const override {
            auto* p = dynamic_cast<const Model<Iter>*>(&other);
            return p && iter == p->iter;
        }
        std::unique_ptr<Concept> clone() const override {
            return std::make_unique<Model<Iter>>(iter);
        }
    };
    std::unique_ptr<Concept> pImpl;
public:
    template <typename Iter>
    AnyIterator(Iter it) : pImpl(std::make_unique<Model<Iter>>(it)) {}
    AnyIterator(const AnyIterator& other)
        : pImpl(other.pImpl ? other.pImpl->clone() : nullptr) {}
    AnyIterator& operator=(const AnyIterator& other) {
        if (this != &other) {
            pImpl = other.pImpl ? other.pImpl->clone() : nullptr;
        }
        return *this;
    }
    AnyIterator(AnyIterator&&) = default;
    AnyIterator& operator=(AnyIterator&&) = default;
    T& operator*() { return pImpl->deref(); }
    AnyIterator& operator++() { pImpl->next(); return *this; }
    bool operator==(const AnyIterator& other) const {
        return pImpl->equal(*other.pImpl);
    }
    bool operator!=(const AnyIterator& other) const {
        return !(*this == other);
    }
};
int main() {
    std::vector<int> vec = {1, 2, 3};
    std::list<int> lst = {4, 5, 6};
    AnyIterator<int> it1 = vec.begin();
    AnyIterator<int> it2 = lst.begin();
    std::cout << *it1 << "\n";  // 1
    std::cout << *it2 << "\n";  // 4
    return 0;
}

8. Advanced Patterns

Pattern 5: Task Queue with Type Erasure

#include <functional>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <iostream>
class TaskQueue {
    std::queue<std::function<void()>> tasks;
    std::mutex mtx;
    std::condition_variable cv;
    bool stop = false;
public:
    template <typename F>
    void enqueue(F&& f) {
        {
            std::lock_guard<std::mutex> lock(mtx);
            tasks.emplace(std::forward<F>(f));
        }
        cv.notify_one();
    }
    void worker() {
        while (true) {
            std::function<void()> task;
            {
                std::unique_lock<std::mutex> lock(mtx);
                cv.wait(lock, [this]() { return stop || !tasks.empty(); });
                if (stop && tasks.empty()) return;
                task = std::move(tasks.front());
                tasks.pop();
            }
            task();
        }
    }
    void shutdown() {
        {
            std::lock_guard<std::mutex> lock(mtx);
            stop = true;
        }
        cv.notify_all();
    }
};
int main() {
    TaskQueue tq;
    std::thread worker([&]() { tq.worker(); });
    tq.enqueue([]() { std::cout << "Task 1\n"; });
    tq.enqueue([]() { std::cout << "Task 2\n"; });
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    tq.shutdown();
    worker.join();
    return 0;
}

Pattern 6: Polymorphic Value Type

#include <memory>
#include <iostream>
template <typename T>
class PolyValue {
    struct Concept {
        virtual ~Concept() = default;
        virtual T get() const = 0;
        virtual std::unique_ptr<Concept> clone() const = 0;
    };
    template <typename U>
    struct Model : Concept {
        U value;
        Model(U v) : value(std::move(v)) {}
        T get() const override { return value; }
        std::unique_ptr<Concept> clone() const override {
            return std::make_unique<Model<U>>(value);
        }
    };
    std::unique_ptr<Concept> pImpl;
public:
    template <typename U>
    PolyValue(U value) : pImpl(std::make_unique<Model<U>>(std::move(value))) {}
    PolyValue(const PolyValue& other)
        : pImpl(other.pImpl ? other.pImpl->clone() : nullptr) {}
    PolyValue& operator=(const PolyValue& other) {
        if (this != &other) {
            pImpl = other.pImpl ? other.pImpl->clone() : nullptr;
        }
        return *this;
    }
    PolyValue(PolyValue&&) = default;
    PolyValue& operator=(PolyValue&&) = default;
    T get() const { return pImpl->get(); }
};
int main() {
    PolyValue<int> v1 = 10;
    PolyValue<int> v2 = 3.14;  // double → int
    std::cout << v1.get() << "\n";  // 10
    std::cout << v2.get() << "\n";  // 3
    return 0;
}

9. Best Practices

9.1 When to Use Type Erasure

  • Plugin systems: Load arbitrary types at runtime.
  • Callback systems: Store various callable types.
  • Generic containers: Store heterogeneous types.
  • API boundaries: Hide implementation details.

9.2 When NOT to Use

  • Performance-critical paths: Template inlining is faster.
  • Simple inheritance suffices: Don’t over-engineer.
  • Type safety needed: std::variant is safer than std::any.

9.3 std::function Performance Tips

  • Avoid frequent copies: Use std::move or references.
  • Small captures: Avoid heap allocation with small functor optimization (SFO).
  • Consider alternatives: For hot paths, use templates or function pointers.

9.4 std::any Safety Tips

  • Type check before cast: Use pointer version of any_cast.
  • Document expected types: Comment which types are stored.
  • Consider std::variant: If types are known, std::variant is safer.

10. Production Examples

Example 1: HTTP Handler Registry

#include <functional>
#include <map>
#include <string>
#include <iostream>
class HttpServer {
    using Handler = std::function<void(const std::string&)>;
    std::map<std::string, Handler> routes;
public:
    void route(const std::string& path, Handler handler) {
        routes[path] = std::move(handler);
    }
    void handleRequest(const std::string& path, const std::string& body) {
        if (auto it = routes.find(path); it != routes.end()) {
            it->second(body);
        } else {
            std::cout << "404 Not Found\n";
        }
    }
};
int main() {
    HttpServer server;
    server.route("/hello", [](const std::string& body) {
        std::cout << "Hello: " << body << "\n";
    });
    server.route("/echo", [](const std::string& body) {
        std::cout << "Echo: " << body << "\n";
    });
    server.handleRequest("/hello", "World");  // Hello: World
    server.handleRequest("/echo", "Test");    // Echo: Test
    server.handleRequest("/unknown", "");     // 404 Not Found
    return 0;
}

Example 2: Validation System

#include <functional>
#include <vector>
#include <string>
#include <iostream>
class Validator {
    std::vector<std::function<bool(const std::string&)>> rules;
public:
    void addRule(std::function<bool(const std::string&)> rule) {
        rules.push_back(std::move(rule));
    }
    bool validate(const std::string& input) const {
        for (const auto& rule : rules) {
            if (!rule(input)) return false;
        }
        return true;
    }
};
int main() {
    Validator v;
    v.addRule([](const std::string& s) { return s.size() >= 8; });
    v.addRule([](const std::string& s) { return s.find_first_of("0123456789") != std::string::npos; });
    std::cout << std::boolalpha;
    std::cout << v.validate("short") << "\n";      // false
    std::cout << v.validate("longtext") << "\n";   // false
    std::cout << v.validate("longtext123") << "\n"; // true
    return 0;
}

Example 3: Logger with Type-Erased Sinks

#include <memory>
#include <vector>
#include <iostream>
#include <fstream>
class Logger {
    struct Sink {
        virtual ~Sink() = default;
        virtual void write(const std::string& msg) = 0;
    };
    template <typename T>
    struct SinkModel : Sink {
        T sink;
        SinkModel(T s) : sink(std::move(s)) {}
        void write(const std::string& msg) override {
            sink.write(msg);
        }
    };
    std::vector<std::unique_ptr<Sink>> sinks;
public:
    template <typename T>
    void addSink(T sink) {
        sinks.push_back(std::make_unique<SinkModel<T>>(std::move(sink)));
    }
    void log(const std::string& msg) {
        for (auto& sink : sinks) {
            sink->write(msg);
        }
    }
};
struct ConsoleSink {
    void write(const std::string& msg) {
        std::cout << "[Console] " << msg << "\n";
    }
};
struct FileSink {
    std::ofstream file;
    FileSink(const std::string& path) : file(path, std::ios::app) {}
    void write(const std::string& msg) {
        file << "[File] " << msg << "\n";
    }
};
int main() {
    Logger logger;
    logger.addSink(ConsoleSink{});
    logger.addSink(FileSink{"log.txt"});
    logger.log("Application started");
    logger.log("Processing data");
    return 0;
}

Summary

  • Type Erasure: Hides type information, handles various types through unified interface.
  • std::function: Function type erasure, stores any callable with matching signature.
  • std::any: Stores arbitrary types, retrieves with any_cast.
  • Manual implementation: Uses Concept (abstract interface) + Model (concrete implementation) pattern.
  • Use cases: Callback systems, plugin systems, generic containers, API boundaries.
  • Tradeoffs: Flexible but slower than templates, involves heap allocation and indirect calls.

Keywords

C++ type erasure, std::function, std::any, polymorphism, design patterns, metaprogramming

Frequently Asked Questions (FAQ)

Q. When to use type erasure in production?

A. When you need runtime polymorphism without inheritance constraints. Examples: plugin systems loading arbitrary types, callback registries storing various callables, generic message queues, API boundaries hiding implementation details. Refer to Practical Use Cases and Production Examples sections.

Q. Type erasure vs virtual functions?

A. Virtual functions require common base class and modifying existing types. Type erasure wraps arbitrary types sharing behavior without inheritance. Choose type erasure when you can’t modify existing types or need more flexibility; choose virtual functions when inheritance hierarchy is natural and performance is critical.

Q. std::any vs std::variant?

A. std::any for truly arbitrary types (runtime checks, slower), std::variant for closed set of known types (compile-time checks, faster/safer). Choose std::any when you cannot enumerate all possible types at compile time; choose std::variant when you know all possible types and want compile-time safety.

Q. How to reduce std::function overhead?

A. Use std::move to avoid copying large captures, keep captures small to leverage small functor optimization (SFO), or use templates directly for hot paths where inlining matters. For performance-critical code, consider function pointers or template parameters instead of std::function. One-line summary: Type erasure provides flexible polymorphism without inheritance, but involves performance tradeoffs. Use for plugin systems, callbacks, and API boundaries where flexibility outweighs overhead.


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. Master C++ Type Erasure: hide concrete types behind stable interfaces for static polymorphism. Complete guide to std::fu… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

C++, type-erasure, std::function, std::any, polymorphism, design patterns, metaprogramming 등으로 검색하시면 이 글이 도움이 됩니다.