[2026] C++ Type Erasure Complete Guide | Unified Interfaces Without Inheritance

[2026] C++ Type Erasure Complete Guide | Unified Interfaces Without Inheritance

이 글의 핵심

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. 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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. 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ 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

아래 코드는 mermaid를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 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. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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. 아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

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

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ 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. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ 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

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

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. 아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 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

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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)

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3