C++ Type Erasure 详解和应用场景
一、什么是Type Erasure?
Type Erasure(类型擦除)是一种C++编程技术,它允许我们在保持静态类型安全的同时,将不同的具体类型统一到同一个抽象接口下。它隐藏了具体类型信息,提供类型无关的接口,同时保留值语义。
简单来说:Type Erasure让你可以把任意类型的对象放进同一个容器,或者通过同一个接口调用它们的方法,就像Java/C#中的接口或基类一样,但不需要共同的基类。
二、Type Erasure的三种经典实现方式
1. 基于虚函数 + 模板派生(最常用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
#include <iostream>
#include <memory>
#include <vector>
// 概念:任何实现了 speak() 的类型
class Speaker {
private:
// 内部抽象基类(概念模型)
struct Concept {
virtual ~Concept() = default;
virtual void speak() 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 speak() const override {
object.speak();
}
std::unique_ptr<Concept> clone() const override {
return std::make_unique<Model>(object);
}
};
std::unique_ptr<Concept> pimpl;
public:
// 构造函数模板 - 接受任何可以speak的类型
template<typename T>
Speaker(T obj) : pimpl(std::make_unique<Model<T>>(std::move(obj))) {}
// 拷贝构造(深拷贝)
Speaker(const Speaker& other) : pimpl(other.pimpl ? other.pimpl->clone() : nullptr) {}
// 移动构造
Speaker(Speaker&&) = default;
// 拷贝赋值
Speaker& operator=(const Speaker& other) {
if (this != &other) {
pimpl = other.pimpl ? other.pimpl->clone() : nullptr;
}
return *this;
}
// 移动赋值
Speaker& operator=(Speaker&&) = default;
// 对外接口
void speak() const {
if (pimpl) pimpl->speak();
}
};
// 实际使用
class Dog {
public:
void speak() const { std::cout << "Woof!\\n"; }
};
class Cat {
public:
void speak() const { std::cout << "Meow!\\n"; }
};
class Person {
public:
void speak() const { std::cout << "Hello!\\n"; }
};
int main() {
std::vector<Speaker> speakers;
speakers.emplace_back(Dog{});
speakers.emplace_back(Cat{});
speakers.emplace_back(Person{});
for (const auto& s : speakers) {
s.speak(); // 输出: Woof! Meow! Hello!
}
return 0;
}
|
2. 基于函数指针(轻量级)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
#include <iostream>
#include <vector>
#include <functional>
// 轻量级Type Erasure - 只擦除单一函数
template<typename Signature>
class Function;
// 特化版本
template<typename Ret, typename... Args>
class Function<Ret(Args...)> {
private:
// 函数指针类型
using Invoker = Ret(*)(void*, Args...);
void* obj_ptr;
Invoker invoker;
// 静态调用函数模板
template<typename T>
static Ret invoke_impl(void* ptr, Args... args) {
return (*static_cast<T*>(ptr))(std::forward<Args>(args)...);
}
public:
template<typename T>
Function(T& obj) : obj_ptr(&obj), invoker(&invoke_impl<T>) {}
Ret operator()(Args... args) const {
return invoker(obj_ptr, std::forward<Args>(args)...);
}
};
// 使用示例
struct Adder {
int operator()(int a, int b) const { return a + b; }
};
struct Multiplier {
int operator()(int a, int b) const { return a * b; }
};
int main() {
Adder adder;
Multiplier multiplier;
Function<int(int, int)> f1(adder);
Function<int(int, int)> f2(multiplier);
std::cout << f1(3, 4) << "\\n"; // 7
std::cout << f2(3, 4) << "\\n"; // 12
return 0;
}
|
3. 基于std::function(标准库实现)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
#include <iostream>
#include <functional>
#include <vector>
// std::function 是C++标准库中最常用的Type Erasure例子
void demo_std_function() {
std::vector<std::function<void()>> callbacks;
int x = 42;
// 可以存储不同类型的可调用对象
callbacks.push_back([]() { std::cout << "Lambda 1\\n"; });
callbacks.push_back([&x]() { std::cout << "Lambda with capture: " << x << "\\n"; });
struct Functor {
void operator()() const { std::cout << "Functor\\n"; }
};
callbacks.push_back(Functor{});
// 统一调用
for (auto& cb : callbacks) {
cb();
}
}
|
三、实际应用场景
场景1:任意类型的容器(类似std::any)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
#include <iostream>
#include <vector>
#include <typeinfo>
#include <memory>
class Any {
private:
struct Concept {
virtual ~Concept() = default;
virtual std::unique_ptr<Concept> clone() const = 0;
virtual const std::type_info& type() const = 0;
};
template<typename T>
struct Model : Concept {
T value;
Model(T v) : value(std::move(v)) {}
std::unique_ptr<Concept> clone() const override {
return std::make_unique<Model>(value);
}
const std::type_info& type() const override {
return typeid(T);
}
};
std::unique_ptr<Concept> pimpl;
public:
template<typename T>
Any(T value) : pimpl(std::make_unique<Model<T>>(std::move(value))) {}
Any(const Any& other) : pimpl(other.pimpl ? other.pimpl->clone() : nullptr) {}
template<typename T>
T* cast() {
if (typeid(T) == pimpl->type()) {
return &static_cast<Model<T>*>(pimpl.get())->value;
}
return nullptr;
}
};
int main() {
std::vector<Any> mixed;
mixed.push_back(42);
mixed.push_back(3.14);
mixed.push_back(std::string("Hello"));
if (auto* p = mixed[0].cast<int>()) {
std::cout << "int: " << *p << "\\n";
}
if (auto* p = mixed[2].cast<std::string>()) {
std::cout << "string: " << *p << "\\n";
}
return 0;
}
|
场景2:鸭子类型(Duck Typing)的实现
鸭子类型(Duck Typing)是一种动态类型的编程风格,其核心思想来自美国诗人James Whitcomb Riley的名言:
“如果它走路像鸭子,叫起来像鸭子,那么它就是鸭子。”
在编程中,鸭子类型的含义是:一个对象的适用性由它实现的方法和行为决定,而不是由它的类型或继承关系决定。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
#include <iostream>
#include <vector>
#include <memory>
// 定义行为概念:可打印
class Printable {
private:
struct Concept {
virtual ~Concept() = default;
virtual void print(std::ostream& os) const = 0;
virtual std::unique_ptr<Concept> clone() const = 0;
};
template<typename T>
struct Model : Concept {
T value;
Model(T v) : value(std::move(v)) {}
void print(std::ostream& os) const override {
os << value; // 要求T支持operator<<
}
std::unique_ptr<Concept> clone() const override {
return std::make_unique<Model>(value);
}
};
std::unique_ptr<Concept> pimpl;
public:
template<typename T>
Printable(T value) : pimpl(std::make_unique<Model<T>>(std::move(value))) {}
Printable(const Printable& other) : pimpl(other.pimpl->clone()) {}
friend std::ostream& operator<<(std::ostream& os, const Printable& p) {
p.pimpl->print(os);
return os;
}
};
// 使用示例 - 任何支持operator<<的类型都可以放入
int main() {
std::vector<Printable> items;
items.emplace_back(42);
items.emplace_back(3.14);
items.emplace_back("Hello");
items.emplace_back(std::string("World"));
for (const auto& item : items) {
std::cout << item << "\\n";
}
return 0;
}
|
场景3:插件系统或运行时多态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
#include <iostream>
#include <vector>
#include <memory>
#include <string>
// 游戏对象接口(类型擦除版本)
class GameObject {
private:
struct Concept {
virtual ~Concept() = default;
virtual void update(float dt) = 0;
virtual void render() const = 0;
virtual std::string name() 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 update(float dt) override {
object.update(dt);
}
void render() const override {
object.render();
}
std::string name() const override {
return object.name();
}
std::unique_ptr<Concept> clone() const override {
return std::make_unique<Model>(object);
}
};
std::unique_ptr<Concept> pimpl;
public:
template<typename T>
GameObject(T obj) : pimpl(std::make_unique<Model<T>>(std::move(obj))) {}
GameObject(const GameObject& other) : pimpl(other.pimpl->clone()) {}
void update(float dt) { pimpl->update(dt); }
void render() const { pimpl->render(); }
std::string name() const { return pimpl->name(); }
};
// 具体的游戏对象 - 不需要继承
class Player {
int health = 100;
public:
void update(float dt) { /* 玩家逻辑 */ }
void render() const { std::cout << "Rendering Player\\n"; }
std::string name() const { return "Player"; }
};
class Enemy {
int type = 1;
public:
void update(float dt) { /* 敌人逻辑 */ }
void render() const { std::cout << "Rendering Enemy\\n"; }
std::string name() const { return "Enemy"; }
};
class PowerUp {
public:
void update(float dt) { /* 道具逻辑 */ }
void render() const { std::cout << "Rendering PowerUp\\n"; }
std::string name() const { return "PowerUp"; }
};
int main() {
std::vector<GameObject> scene;
scene.emplace_back(Player{});
scene.emplace_back(Enemy{});
scene.emplace_back(PowerUp{});
// 统一处理所有对象
for (auto& obj : scene) {
std::cout << "Updating " << obj.name() << "\\n";
obj.update(0.016f);
obj.render();
}
return 0;
}
|
场景4:避免模板代码膨胀
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
// 不使用Type Erasure - 每个类型都会生成一份代码
template<typename Container>
void process_container_template(const Container& c) {
std::cout << "Processing container of size: " << c.size() << "\\n";
// 大量代码...
}
// 使用Type Erasure - 只生成一份代码
class ContainerView {
private:
struct Concept {
virtual ~Concept() = default;
virtual size_t size() const = 0;
virtual void* begin() = 0;
virtual void* end() = 0;
};
template<typename T>
struct Model : Concept {
T& container;
Model(T& c) : container(c) {}
size_t size() const override { return container.size(); }
void* begin() override { return &*container.begin(); }
void* end() override { return &*container.end(); }
};
std::unique_ptr<Concept> pimpl;
public:
template<typename T>
ContainerView(T& c) : pimpl(std::make_unique<Model<T>>(c)) {}
size_t size() const { return pimpl->size(); }
void* begin() { return pimpl->begin(); }
void* end() { return pimpl->end(); }
};
// 只生成一份代码
void process_container_erased(ContainerView c) {
std::cout << "Processing container of size: " << c.size() << "\\n";
// 大量代码只生成一份
}
int main() {
std::vector<int> v = {1, 2, 3};
std::vector<double> v2 = {1.1, 2.2, 3.3};
process_container_erased(v); // 调用同一份代码
process_container_erased(v2); // 调用同一份代码
return 0;
}
|
四、Type Erasure 与其他技术对比
| 技术 |
优点 |
缺点 |
适用场景 |
| Type Erasure |
值语义,无侵入,灵活 |
有虚函数开销,实现复杂 |
需要存储异构类型 |
| 继承+虚函数 |
简单直接,标准OOP |
侵入式,需要公共基类 |
已知类型层次结构 |
| 模板 |
零开销抽象 |
代码膨胀,编译慢 |
编译期多态 |
| std::variant |
类型安全,无堆分配 |
类型集合固定 |
有限的异构类型 |
五、性能考虑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
#include <iostream>
#include <chrono>
#include <vector>
#include <functional>
// Type Erasure 有虚函数调用开销
// 但值语义带来更好的缓存局部性
class Drawable {
// ... 实现同前 ...
};
// 对比传统虚函数
struct Shape {
virtual void draw() const = 0;
virtual ~Shape() = default;
};
struct Circle : Shape {
void draw() const override {}
};
int main() {
constexpr int N = 1000000;
// Type Erasure - 值语义,连续内存
std::vector<Drawable> erased;
// ... 填充 ...
// 虚函数 - 指针语义,内存不连续
std::vector<Shape*> pointers;
// ... 填充 ...
// 访问模式不同,Type Erasure通常有更好的缓存性能
// 但每次调用有间接跳转
}
|
六、现代C++中的Type Erasure
C++20引入了concepts,可以与type erasure结合:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
#include <concepts>
#include <iostream>
template<typename T>
concept Drawable = requires(T t) {
{ t.draw() } -> std::same_as<void>;
};
// 概念约束的Type Erasure
class Canvas {
private:
struct Concept {
virtual ~Concept() = default;
virtual void draw() const = 0;
};
template<Drawable T>
struct Model : Concept {
T obj;
Model(T o) : obj(std::move(o)) {}
void draw() const override { obj.draw(); }
};
std::unique_ptr<Concept> pimpl;
public:
template<Drawable T>
Canvas(T obj) : pimpl(std::make_unique<Model<T>>(std::move(obj))) {}
void draw() const { pimpl->draw(); }
};
|
七、总结
Type Erasure的核心价值:
- 类型无关:统一处理不同类型的对象
- 非侵入式:不需要修改原有类型
- 值语义:便于存储和管理
- 编译期类型安全:运行时擦除,但构造时检查
何时使用Type Erasure:
- 需要存储异构类型集合(如std::vector)
- 实现鸭子类型(Duck Typing)
- 减少模板代码膨胀
- 提供类型擦除的接口(如std::function)
- 插件系统或运行时多态(不想强制继承)
何时避免:
- 对性能极其敏感(虚函数调用开销)
- 类型集合已知且固定(使用std::variant更好)
- 简单场景,继承足够