C++ Type Erasure 详解

C++ learning

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的核心价值

  1. 类型无关:统一处理不同类型的对象
  2. 非侵入式:不需要修改原有类型
  3. 值语义:便于存储和管理
  4. 编译期类型安全:运行时擦除,但构造时检查

何时使用Type Erasure

  • 需要存储异构类型集合(如std::vector
  • 实现鸭子类型(Duck Typing)
  • 减少模板代码膨胀
  • 提供类型擦除的接口(如std::function)
  • 插件系统或运行时多态(不想强制继承)

何时避免

  • 对性能极其敏感(虚函数调用开销)
  • 类型集合已知且固定(使用std::variant更好)
  • 简单场景,继承足够
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy