文档目录

一、问题场景

场景:你有一个容器,想存"满足某个接口的任何类型",但又不希望用户继承你的基类。

传统做法是定义一个抽象基类,用户必须继承它:

class Drawable {
public:
    virtual void draw() const = 0;
    virtual ~Drawable() = default;
};

// 用户必须继承 Drawable
class MyCircle : public Drawable { ... };

问题:用户需要用 public 继承你的类,这是侵入式的。用户可能已经有了自己的类层次,不想再改继承。

Type Erasure 的做法:非侵入式——用户不需要继承任何东西。你提供包装器。

二、Type Erasure 的核心模式

Type Erasure 的经典实现是 外部多态(External Polymorphism):

// 1. 用 concept 定义接口
template <typename T>
concept Drawable = requires(const T& t) {
    { t.draw() } -> std::same_as<void>;
};

// 2. 类型擦除包装器(用户可以传入任何满足 Drawable 的类型)
class DrawableWrapper {
    struct Concept {
        virtual void draw() const = 0;
        virtual ~Concept() = default;
    };

    template <Drawable T>
    struct Model : Concept {
        T obj_;
        Model(T obj) : obj_(std::move(obj)) {}
        void draw() const override { obj_.draw(); }
    };

    std::unique_ptr<Concept> pimpl_;

public:
    template <Drawable T>
    DrawableWrapper(T obj) : pimpl_(std::make_unique<Model<T>>(std::move(obj))) {}

    void draw() const { pimpl_->draw(); }
};

用法:

struct Circle {
    void draw() const { std::cout << "○"; }
};
struct Square {
    void draw() const { std::cout << "□"; }
};

std::vector<DrawableWrapper> shapes;
shapes.emplace_back(Circle{});
shapes.emplace_back(Square{});

for (const auto& s : shapes) {
    s.draw();  // 输出 ○□
}

用户不需要继承任何东西。只要类型有 draw() 方法,就能被 DrawableWrapper 包装。

三、Type Erasure 的底层结构

DrawableWrapper 的内存布局:
栈上:
+──────────────────────────+
| pimpl_(unique_ptr)       |──→ 堆上: Model<Circle>
+──────────────────────────+      +──────────────+
                                  | vptr         │──→ vtable
                                  | obj_ (Circle)    (draw -> Circle::draw)
                                  +──────────────+

每次调用 draw() 仍然有一次虚函数调用(通过 pimpl_ 的 vtable),但这个虚函数调用被封装在包装器内部,用户看不见。

四、Type Erasure 和虚基类的对比

对比 虚基类(侵入式) Type Erasure(非侵入式)
用户是否需要继承 是 否
性能 一次虚函数调用 一次虚函数调用(和虚基类一样)
对已有类型的适配 不能适配已有的类 可以,只要类型有对应方法
实现复杂度 简单 复杂(需要自己写包装器)