在现代 C++ 开发中,经常需要将不同类型的对象统一存放在同一个容器中,而不想使用多态基类或模板化。类型擦除(Type Erasure)是一种技术,能够在保持编译期类型信息的同时,在运行时实现对不同类型的统一处理。下面将演示如何在 C++17 标准下实现一个简易的类型擦除容器 any_container,它可以存放任何可拷贝、可移动、可比较的对象,并支持遍历、查找和删除。
1. 设计思路
- 抽象接口
定义一个Concept抽象基类,声明所需的虚函数:clone():返回对象的深拷贝compare(const void*):实现类型安全的比较to_string():用于调试输出
- 模型实现
对每个具体类型T,实现一个 `Model `,继承自 `Concept`。 - 类型擦除包装器
AnyHolder包含一个 `std::unique_ptr `,提供拷贝构造、移动构造、赋值等。 - 容器
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. 进一步改进
- 支持可比较性
目前equals仅对相同类型使用==。若需要对不同类型之间的等价性进行自定义,可在Concept中加入虚函数bool equal(const Concept*) const并在模型中实现。 - 移动语义
AnyContainer::push_back已实现移动构造,进一步可在容器中提供emplace_back接口,直接构造对象。 - 迭代器改进
目前begin()返回AnyHolder,无法直接访问存储的原始类型。可通过模板函数 `any_cast ` 对迭代器返回值进行类型恢复,类似 `std::any_cast` 的实现。
6. 小结
本文展示了在 C++17 中实现通用类型擦除容器的完整流程。通过抽象基类、模板模型、类型擦除包装器以及容器包装,实现了对任意可拷贝、可移动、可比较对象的统一存放与管理。该技术在需要混合类型数据、插件系统或实现轻量级多态时具有广泛应用价值。