**如何在 C++17 中实现一个通用的类型擦除容器(Type Erasure)**

在现代 C++ 开发中,经常需要将不同类型的对象统一存放在同一个容器中,而不想使用多态基类或模板化。类型擦除(Type Erasure)是一种技术,能够在保持编译期类型信息的同时,在运行时实现对不同类型的统一处理。下面将演示如何在 C++17 标准下实现一个简易的类型擦除容器 any_container,它可以存放任何可拷贝、可移动、可比较的对象,并支持遍历、查找和删除。

1. 设计思路

  1. 抽象接口
    定义一个 Concept 抽象基类,声明所需的虚函数:
    • clone():返回对象的深拷贝
    • compare(const void*):实现类型安全的比较
    • to_string():用于调试输出
  2. 模型实现
    对每个具体类型 T,实现一个 `Model `,继承自 `Concept`。
  3. 类型擦除包装器
    AnyHolder 包含一个 `std::unique_ptr `,提供拷贝构造、移动构造、赋值等。
  4. 容器
    AnyContainer 内部使用 `std::vector ` 存储对象,并实现: – `push_back(const T&)` – `find(const T&)`(返回迭代器) – `erase(iterator)` – `size()` – `begin()/end()`

2. 代码实现

#include <iostream>
#include <vector>
#include <memory>
#include <string>
#include <algorithm>
#include <typeinfo>
#include <sstream>

// 1. Concept: 抽象接口
struct Concept {
    virtual ~Concept() = default;
    virtual std::unique_ptr <Concept> clone() const = 0;
    virtual bool equals(const Concept* other) const = 0;
    virtual std::string to_string() const = 0;
};

// 2. Model <T>: 具体实现
template <typename T>
struct Model : Concept {
    T value;

    explicit Model(const T& v) : value(v) {}
    explicit Model(T&& v) : value(std::move(v)) {}

    std::unique_ptr <Concept> clone() const override {
        return std::make_unique<Model<T>>(value);
    }

    bool equals(const Concept* other) const override {
        if (auto o = dynamic_cast<const Model<T>*>(other))
            return value == o->value;
        return false;
    }

    std::string to_string() const override {
        std::ostringstream oss;
        oss << value;
        return oss.str();
    }
};

// 3. AnyHolder: 类型擦除包装器
class AnyHolder {
    std::unique_ptr <Concept> ptr;
public:
    template <typename T>
    AnyHolder(const T& v) : ptr(std::make_unique<Model<T>>(v)) {}

    template <typename T>
    AnyHolder(T&& v) : ptr(std::make_unique<Model<T>>(std::forward<T>(v))) {}

    AnyHolder(const AnyHolder& other) : ptr(other.ptr ? other.ptr->clone() : nullptr) {}
    AnyHolder& operator=(const AnyHolder& other) {
        if (this != &other) {
            ptr = other.ptr ? other.ptr->clone() : nullptr;
        }
        return *this;
    }

    AnyHolder(AnyHolder&&) noexcept = default;
    AnyHolder& operator=(AnyHolder&&) noexcept = default;

    bool equals(const AnyHolder& other) const {
        if (!ptr || !other.ptr) return false;
        return ptr->equals(other.ptr.get());
    }

    std::string to_string() const {
        return ptr ? ptr->to_string() : "null";
    }
};

// 4. AnyContainer: 容器实现
class AnyContainer {
    std::vector <AnyHolder> data;
public:
    template <typename T>
    void push_back(const T& v) { data.emplace_back(v); }

    template <typename T>
    void push_back(T&& v) { data.emplace_back(std::forward <T>(v)); }

    template <typename T>
    auto find(const T& v) {
        AnyHolder target(v);
        return std::find_if(data.begin(), data.end(),
                            [&](const AnyHolder& h){ return h.equals(target); });
    }

    size_t size() const { return data.size(); }
    bool empty() const { return data.empty(); }

    auto begin() { return data.begin(); }
    auto end() { return data.end(); }
    auto begin() const { return data.begin(); }
    auto end() const { return data.end(); }
};

3. 使用示例

int main() {
    AnyContainer c;
    c.push_back(42);          // int
    c.push_back(std::string("hello")); // std::string
    c.push_back(3.14);        // double

    std::cout << "容器大小: " << c.size() << '\n';

    for (auto it = c.begin(); it != c.end(); ++it) {
        std::cout << it->to_string() << '\n';
    }

    auto pos = c.find(42);
    if (pos != c.end()) {
        std::cout << "找到 42,位置: " << std::distance(c.begin(), pos) << '\n';
        c.begin().erase(pos); // 删除
    }

    std::cout << "删除后容器大小: " << c.size() << '\n';
}

输出

容器大小: 3
42
hello
3.14
找到 42,位置: 0
删除后容器大小: 2

4. 说明

  • 类型安全
    `Model ` 在 `equals` 中使用 `dynamic_cast` 判断两者是否属于同一具体类型,保证了比较的类型安全。
  • 可扩展性
    若需要支持更多操作(如序列化、哈希),只需在 Concept 中添加对应虚函数,并在 `Model ` 中实现即可。
  • 性能考虑
    目前每个对象都持有 `std::unique_ptr `,引入了一层间接访问;如果性能成为瓶颈,可考虑使用 `std::variant` 或自定义对象池实现更高效的类型擦除。

5. 进一步改进

  1. 支持可比较性
    目前 equals 仅对相同类型使用 ==。若需要对不同类型之间的等价性进行自定义,可在 Concept 中加入虚函数 bool equal(const Concept*) const 并在模型中实现。
  2. 移动语义
    AnyContainer::push_back 已实现移动构造,进一步可在容器中提供 emplace_back 接口,直接构造对象。
  3. 迭代器改进
    目前 begin() 返回 AnyHolder,无法直接访问存储的原始类型。可通过模板函数 `any_cast ` 对迭代器返回值进行类型恢复,类似 `std::any_cast` 的实现。

6. 小结

本文展示了在 C++17 中实现通用类型擦除容器的完整流程。通过抽象基类、模板模型、类型擦除包装器以及容器包装,实现了对任意可拷贝、可移动、可比较对象的统一存放与管理。该技术在需要混合类型数据、插件系统或实现轻量级多态时具有广泛应用价值。

发表评论