类型擦除通过基类虚函数统一接口,模板派生类保存具体可调用对象,包装器持基类指针实现值语义,支持任意可调用类型但隐藏具体类型,如简易std::function实现所示。

类型擦除(Type Erasure)是一种让不同类型共享统一接口的技术,常用于实现类似 std::function 的泛型函数包装器。它能隐藏具体类型信息,只暴露行为接口,从而实现运行时多态而无需继承体系。
基本思路:封装可调用对象
目标是实现一个通用的函数包装器,支持任意可调用类型(函数指针、lambda、仿函数等),但对外呈现统一类型。核心是把“具体类型”擦除,只保留“调用能力”。
我们通过以下组件实现:
- 基类接口:定义调用操作的虚函数
- 模板派生类:保存具体可调用对象
- 包装器类:持有基类指针,提供值语义
手动实现简易 std::function
下面是一个简化版的函数包装器,支持无参返回 int 的调用:
立即学习“C++免费学习笔记(深入)”;
#include <memory>
#include <iostream>
class Function {
struct CallableBase {
virtual ~CallableBase() = default;
virtual int call() = 0;
virtual std::unique_ptr<CallableBase> clone() const = 0;
};
template <typename F>
struct CallableModel : CallableBase {
F func;
explicit CallableModel(F f) : func(std::move(f)) {}
int call() override { return func(); }
std::unique_ptr<CallableBase> clone() const override {
return std::make_unique<CallableModel>(func);
}
};
std::unique_ptr<CallableBase> callable;
public:
Function() = default;
template <typename F>
Function(F f) : callable(std::make_unique<CallableModel<F>>(std::move(f))) {}
Function(const Function& other)
: callable(other.callable ? other.callable->clone() : nullptr) {}
Function& operator=(const Function& other) {
if (this != &other) {
callable = other.callable ? other.callable->clone() : nullptr;
}
return *this;
}
Function(Function&&) = default;
Function& operator=(Function&&) = default;
int operator()() const {
if (!callable) throw std::bad_function_call();
return callable->call();
}
explicit operator bool() const { return !!callable; }
};
使用示例:
int foo() { return 42; }
int main() {
Function f1 = []{ return 100; };
Function f2 = foo;
std::cout << f1() << "\n"; // 输出 100
std::cout << f2() << "\n"; // 输出 42
Function f3 = f1;
std::cout << f3() << "\n"; // 输出 100
}
支持任意签名和参数
要支持不同函数签名,可将 Function 改为模板:
template <typename Signature>
class Function;
template <typename R, typename... Args>
class Function<R(Args...)> {
struct CallableBase {
virtual ~CallableBase() = default;
virtual R call(Args... args) = 0;
virtual std::unique_ptr<CallableBase> clone() const = 0;
};
template <typename F>
struct CallableModel : CallableBase {
F func;
explicit CallableModel(F f) : func(std::move(f)) {}
R call(Args... args) override { return func(std::forward<Args>(args)...); }
std::unique_ptr<CallableBase> clone() const override {
return std::make_unique<CallableModel>(func);
}
};
std::unique_ptr<CallableBase> callable;
public:
Function() = default;
template <typename F>
Function(F f) : callable(std::make_unique<CallableModel<F>>(std::move(f))) {}
Function(const Function& other)
: callable(other.callable ? other.callable->clone() : nullptr) {}
Function& operator=(const Function& other) {
if (this != &other) {
callable = other.callable ? other.callable->clone() : nullptr;
}
return *this;
}
Function(Function&&) = default;
Function& operator=(Function&&) = default;
R operator()(Args... args) const {
if (!callable) throw std::bad_function_call();
return callable->call(std::forward<Args>(args)...);
}
explicit operator bool() const { return !!callable; }
};
性能与优化考虑
上述实现每次调用都有虚函数开销。实际如 std::function 会结合小对象优化(Small Buffer Optimization),在栈上存储小型可调用对象,避免堆分配。
关键点:
- 类型擦除牺牲部分性能换取接口统一
- 避免频繁拷贝包装器,尤其含大闭包的 lambda
- 注意异常安全和资源管理(使用智能指针)
基本上就这些。不复杂但容易忽略细节,比如正确实现拷贝语义和所有权转移。











