Type Erasure
TL;DR
- std::function๊ณผ std::shared_ptr๋ ๋ชจ๋ Type Erasure ๊ธฐ๋ฐ์ผ๋ก ๊ตฌํ๋๋ค.
- Type Erasure = ์๋ก ๊ด๊ณ์๋ ๋ค์ํ ํ์ ์ ๋จ์ผ ์ธํฐํ์ด์ค๋ก ๊ฐ์ธ ๊ณตํต์ ์ผ๋ก ๋ค๋ฃจ๋ ๊ธฐ๋ฒ.
- std::function์ ํธ์ถ ์ธํฐํ์ด์ค(call operator)๋ฅผ ์จ๊ธฐ๊ธฐ ์ํด ConceptโModel ๊ตฌ์กฐ๋ฅผ ์ฌ์ฉํ๋ค.
- std::shared_ptr๋ deleter(์๋ฉธ์ ํธ์ถ ๋ฐฉ์)๋ฅผ ์จ๊ธฐ๊ธฐ ์ํด ๊ฐ์ ํจํด์ ์ฌ์ฉํ๋ค.
- ๊ณตํต์ :
- โ์๋ ํ์ ์ ๋ณดโ๋ฅผ ๋ด๋ถ์ ์ ์ฅํด ๋ฐํ์์ ์ฌ๋ฐ๋ฅธ ๋์(ํธ์ถ/์๋ฉธ)์ ๋ณด์ฅํ๋ค.
- ๋ด๋ถ์ ์ผ๋ก virtual dispatch + heap allocation์ด ๋ฐ์ํ๋ค.
Overview
1) ์ std::function์ด ๊ฐ๋ฅํ๊ฐ?
std::function<void(int)>์ด๋ผ๋ ๋จ์ผ ํ์ ์- free function
- lambda
- functor
์์ ํ ์ข ๋ฅ๊ฐ ๋ค๋ฅธ ๊ฐ์ฒด๋ฅผ ๋ชจ๋ ๋์ ํ ์ ์๋ค.
- C++์ ๊ฐํ ์ ์ ํ์ ์ธ์ด์ธ๋ฐ ์ด๋ฐ ์ผ์ด ๊ฐ๋ฅํ ์ด์ ๋ Type Erasure ๋๋ถ.
2) Type Erasure ํต์ฌ ๊ฐ๋
| ๊ตฌ์ฑ | ์ค๋ช |
|---|---|
| Concept(์ธํฐํ์ด์ค) | ํธ์ถํด์ผ ํ๋ ๊ธฐ๋ฅ๋ง ์ ์๋ ์์ ๊ฐ์ ํด๋์ค |
| Model |
T ํ์ ์ ๊ฐ์ธ Concept์ ๊ตฌํํ ๋ํผ |
| Container | Concept*๋ฅผ ๋ฉค๋ฒ๋ก ๊ฐ๊ณ , ํธ์ถ ์ virtual dispatch๋ก Model |
์ฆ,
์ฌ๋ฌ ํ์
(T) โ Model<T>๋ก ๊ฐ์ธ๊ธฐ โ _Concept ํํ๋ก ์ ์ฅ_* โ ๊ณตํต ์ธํฐํ์ด์ค์ฒ๋ผ ์ฌ์ฉ
์ด ํ๋ฆ์ด std::function์ ์ ์ฒด๋ค.
3) std::shared_ptr๊ฐ ๋์ผ ๊ธฐ๋ฒ์ ์ฐ๋ ์ด์
std::shared_ptr<void>๋ ๊ฐ๋ฅํ๋ฐstd::unique_ptr<void>๋ ๊ธฐ๋ณธ deleter ๋๋ฌธ์ ์ญ์ ๊ฐ ๋ถ๊ฐ๋ฅํ๋ค.
์?
unique_ptr<void>โ deleter๊ฐdefault_delete<void>๋ผ์ _void delete๊ฐ ๋ถ๊ฐ๋ฅ_*shared_ptr<void>โ ๊ฐ์ฒด๋ฅผ ์์ฑํ ๋ ์๋ ๊ฐ์ฒด ํ์ T๋ฅผ control block์ ๋ณด๊ด โ ์ญ์ ํ ๋ T์ destructor ํธ์ถ OK
์ฆ, shared_ptr ๋ด๋ถ์๋
- control block + virtual deleter ๊ตฌ์กฐ์ Type Erasure ์กด์ฌ.
4) std::function์ ๋น์ฉ
- virtual ํธ์ถ ๋น์ฉ
- heap allocation ๋น์ฉ
- ๋จ, ๊ตฌํ์ฒด์ ๋ฐ๋ผ SBO(Small Buffer Optimization)๋ก stack ์ ์ฅ๋ ์๋ ์์
Example
1) std::function Type Erasure ๊ตฌ์กฐ (์ถ์ฝ ๋ฒ์ )
struct Concept {
virtual void call(int) = 0;
virtual ~Concept() = default;
};
template<typename T>
struct Model : Concept {
T obj;
Model(T o) : obj(o) {}
void call(int x) override { obj(x); }
};
class Function {
std::unique_ptr<Concept> ptr;
public:
template<typename F>
Function(F f) : ptr(std::make_unique<Model<F>>(f)) {}
void operator()(int x) { ptr->call(x); }
};
์ด๋ ๊ฒ ํ๋ฉด ๋ค์์ด ๋ชจ๋ ์๋ํ๋ค:
Function f1 = printNum;
Function f2 = [](int x){ std::cout << x; };
Function f3 = PrintNumFunctor{};
2) shared_ptr Type Erasure ๊ตฌ์กฐ
struct ControlBlockBase {
size_t refcount = 1;
virtual void destroy() = 0;
virtual ~ControlBlockBase() = default;
};
template<typename T>
struct ControlBlock : ControlBlockBase {
T* ptr;
ControlBlock(T* p) : ptr(p) {}
void destroy() override { delete ptr; }
};
shared_ptr๋ ๋ค์์ ์ ์ฅํ๋ค:
T* obj_ptrControlBlockBase* ctrl
template<typename T>
class SharedPtr {
T* ptr;
ControlBlockBase* ctrl;
public:
template<typename U>
SharedPtr(U* p)
: ptr(p),
ctrl(new ControlBlock<U>(p)) {}
};
์ฆ, U ํ์
์ ๋ณด๊ดํ๋ ControlBlock<U> ๋์
shared_ptr<void>๋ก๋ ์๋ ํ์
์ ์ ํํ๊ฒ ์ญ์ ๊ฐ๋ฅํ๋ค.
Takeaways
std::function
- ์ฌ๋ฌ ํ์ ์ โํธ์ถ ๊ฐ๋ฅํ ๊ฐ์ฒดโ๋ก ์ถ์ํํ๋ ์ปจํ ์ด๋
- ConceptโModelโContainer ๊ตฌ์กฐ๋ฅผ ์ด์ฉํด ํ์ ์ ๋ณด๋ฅผ ์จ๊ธด๋ค
- overhead: virtual call + heap alloc
- ํ์ง๋ง ์ฌ์ฉ์ฑ์ ๊ฐ๋ ฅํจ
std::shared_ptr
shared_ptr<void>๊ฐ ์์ ํ๊ฒ ๋์ํ๋ ์ด์ ๋
โ ๋ฉ๋ชจ๋ฆฌ ๋ธ๋ก ์ญ์ ์ ์๋ ๊ฐ์ฒด ํ์ ์ deleter๋ฅผ ํธ์ถํ๊ธฐ ๋๋ฌธ- ์ด deleter ๋ํ virtual ๊ธฐ๋ฐ Type Erasure ๊ตฌ์กฐ
unique_ptr<void>์์ delete๊ฐ ๋ถ๊ฐ๋ฅํ ์ด์ ๋ ๊ฐ์ ์๋ฆฌ
- Type Erasure๋ C++ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ณณ๊ณณ์์ ํต์ฌ์ ์ธ ์ญํ ์ ํ๋ค.
- โ์ด์ง์ ์ธ ํ์ ๋ค์ ๊ณตํต ์ธํฐํ์ด์ค๋ก ๋ค๋ฃจ๊ธฐโ ์ํด ์ฌ์ฉํ๋ ๊ฐ๋ ฅํ ํจํด.
- std::function / std::shared_ptr / std::any / std::packaged_task ๋ฑ์์ ๋๋ฆฌ ์ฌ์ฉ๋จ.
Reference
Unveiling C++ย Type Erasureย - From std::function to std::any - Sarthak Sehgal - C++Online 2025 CppNorth 2025, Sarthak Sehgal - Unveiling Type Erasure in C++: From std::function to std::any
Type erasure โ Part I, Andrzejโs C++ blog Type erasure โ Part III, Andrzejโs C++ blog Type erasure โ Part III, Andrzejโs C++ blog Type erasure โ Part IV, Andrzejโs C++ blog
Back to Basics: Type Erasure - Arthur OโDwyer - CppCon 2019