[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
| Feature | Virtual Functions | Type 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
- std::function: Function Type Erasure
- std::any: Type Erasure for Arbitrary Types
- Manual Type Erasure Implementation
- Practical Use Cases
- Common Issues
- Comparison Table
- 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_castthrowsstd::bad_any_castif 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 definingdraw()virtual function.Model<T>: Template class inheritingConcept, holding concrete typeTobject.Drawable: External interface holdingunique_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
| Feature | Virtual Functions | Templates | Type Erasure |
|---|---|---|---|
| Compile-time polymorphism | ❌ | ✅ | ❌ |
| Runtime polymorphism | ✅ | ❌ | ✅ |
| Inheritance required | ✅ | ❌ | ❌ |
| Type info maintained | ✅ | ✅ | ❌ |
| Performance | Medium | Fast | Medium |
| Binary size | Small | Large (template bloat) | Medium |
| Flexibility | Low | High | High |
std::any vs std::variant vs std::optional
| Feature | std::any | std::variant | std::optional |
|---|---|---|---|
| Stored types | Arbitrary | Fixed set | Single type |
| Type check | Runtime | Compile-time | N/A |
| Performance | Slow | Fast | Fast |
| Safety | Runtime exception | Compile-time | Compile-time |
| Use case | Unknown types | Known alternatives | May/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::variantis safer thanstd::any.
9.3 std::function Performance Tips
- Avoid frequent copies: Use
std::moveor 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::variantis 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.
Related Articles
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.