C++ 类型擦除详解
C++ 类型擦除详解
类型擦除让 C++ 处理运行时未知类型。核心:隐藏具体类型,只暴露接口。标准库如 std::function, std::any, std::variant 都依赖它。
基于 C++11+,重点 C++17/20 改进。直奔主题:概念、原理、标准实现、手动示例、优缺点。
1. 类型擦除是什么?为什么用?
定义:运行时统一处理多种类型,编译时忘掉具体类型。只剩接口。
痛点解决:
- 模板太静态,无法动态类型(如插件、网络数据)。
- 容器存异构类型:用 Any 包装。
- API 解耦:接受任意 callable,不限 lambda/functor。
不是语法,是模式:动态多态 + vtable。
2. 实现原理
三件套:
- 接口:纯虚基类(clone, destroy, type)。
- Holder:模板派生,持值,实现虚函数。
- 包装器:持
Base*,转发调用。
流程:
- 存
T val→ 创建Holder<T>。 unique_ptr<Base>指向它 → 类型擦除。- 调用 → vtable 分发。
优化:小对象优化(SBO),栈存小 T,避免堆。
vs 模板:运行时开销(虚调用、heap),但灵活。
vs variant:variant 静态知所有类型,无 heap。
3. 标准库示例
std::any (C++17)
存任意 T,用 any_cast<T*> 取出。
简化伪实现(C++11,纯堆分配示意,核心 vtable 类型擦除):
#include <memory>
#include <typeinfo>
#include <type_traits>
#include <stdexcept>
#include <utility>
struct AnyVTable {
void* (*clone)(const void* src);
void (*destroy)(void* ptr);
const std::type_info& (*type)();
};
template<typename T>
const AnyVTable* get_vtable() {
static const AnyVTable vt {
[](const void* src) -> void* {
return new T(*static_cast<const T*>(src));
},
[](void* ptr) {
static_cast<T*>(ptr)->~T();
delete static_cast<T*>(ptr);
},
[]() -> const std::type_info& { return typeid(T); }
};
return &vt;
}
class Any {
private:
void* ptr_;
const AnyVTable* vtable_;
public:
template<typename Value>
Any(Value&& val)
: ptr_(new std::decay_t<Value>(std::forward<Value>(val)))
, vtable_(get_vtable<std::decay_t<Value>>()) {}
Any(const Any& other)
: vtable_(other.vtable_)
, ptr_(other.vtable_->clone(other.ptr_)) {}
~Any() {
vtable_->destroy(ptr_);
}
template<typename T>
T* cast() {
if (vtable_->type() != typeid(T)) {
throw std::bad_any_cast{};
}
return static_cast<T*>(ptr_);
}
};std::function (C++11)
统一 callable。
伪实现类似:vtable 有 invoke。
用时:std::function<int(int)> f = [](int x){return x*x;};
4. 手动实现 Any
完整 C++11 Any,支持拷贝/移动/类型检查。
#include <memory>
#include <typeinfo>
#include <stdexcept>
#include <utility>
#include <iostream>
#include <string>
class Any {
public:
template<typename T>
Any(T&& value) : ptr_(make_holder(std::forward<T>(value))) {}
Any(const Any& other) : ptr_(other.ptr_ ? other.ptr_->clone() : nullptr) {}
Any(Any&& other) noexcept : ptr_(std::move(other.ptr_)) {}
Any& operator=(const Any& other) {
if (this != &other) {
ptr_ = other.ptr_ ? other.ptr_->clone() : nullptr;
}
return *this;
}
Any& operator=(Any&& other) noexcept {
ptr_ = std::move(other.ptr_);
return *this;
}
~Any() = default;
template<typename T>
T& get() & {
auto* holder = dynamic_cast<Holder<T>*>(ptr_.get());
if (!holder) throw std::bad_any_cast{};
return holder->value_;
}
template<typename T>
const T& get() const& {
auto* holder = dynamic_cast<Holder<T>*>(ptr_.get());
if (!holder) throw std::bad_any_cast{};
return holder->value_;
}
bool empty() const { return !ptr_; }
private:
struct Concept {
virtual ~Concept() = default;
virtual Concept* clone() const = 0;
};
template<typename T>
struct Holder : Concept {
T value_;
Holder(T&& v) : value_(std::forward<T>(v)) {}
Concept* clone() const override { return new Holder(value_); }
};
std::unique_ptr<Concept> ptr_;
};测试:
int main() {
Any a = 42;
Any b = std::string("hello");
std::cout << a.get<int>() << '\n'; // 42
std::cout << b.get<std::string>() << '\n'; // hello
return 0;
}关键:dynamic_cast 类型安全。拷贝用 clone()。
5. 优缺点
优点:
- 运行时灵活:插件、事件。
- 统一接口,少模板代码。
缺点:
- 性能烂:虚调用 + heap(用 variant 替代)。
- 调试难:类型隐藏。
- 大小大:多 pointer。
何时用:真动态需求。先模板/variant。
6. 扩展
- Boost.TypeErasure:高级。
- C++20 concepts:约束模板擦除。
用标准库,别重造。除非特殊需求。





