请教类型擦除及std::function中"switch and op code"技术细节
"Switch and Op Code":std::function的一种底层实现模式
定义
这里提到的"switch and op code"是一种用操作码枚举配合switch分支实现类型擦除的模式,曾是std::function的经典实现思路之一:
- 给每个需要支持的操作(比如调用、销毁、拷贝)分配唯一的"操作码"(用枚举表示)
- 将具体类型的操作逻辑封装成统一签名的函数,把函数指针和对应操作码绑定
- 运行时通过操作码,用switch语句分发到对应类型的具体实现,从而隐藏底层真实类型——这就是类型擦除的核心:对外暴露统一接口,内部根据操作码适配不同类型
设计动机
这种模式被用于std::function的核心原因:
- 兼容早期C++环境:在C++11之前,模板元编程和虚函数优化尚不完善,这种方式用简单语法就能实现类型擦除,无需复杂模板技巧
- 灵活可控:可以精准定义支持的操作集合,比如只保留调用逻辑、省略拷贝,避免不必要的性能开销
- 性能优势:相比虚函数的间接调用,switch分支可被编译器优化为跳转表,减少虚表查找的开销,尤其在操作类型较少的场景下更明显
使用示例(简化版std::function)
下面是一个用该模式实现的轻量级可调用对象包装器,模拟std::function<void()>的核心逻辑:
#include <cstdint> #include <memory> #include <iostream> // 定义操作码:标记需要执行的操作类型 enum class OpCode : uint8_t { Invoke, // 调用可调用对象 Destroy, // 销毁底层对象 }; // 统一的函数指针签名,适配所有操作 using FuncPtr = void(*)(void*); // 类型擦除的核心存储结构 struct ErasedWrapper { void* data; // 指向真实可调用对象的指针 FuncPtr handlers[2]; // 对应操作码的处理函数指针 }; // 模板生成具体类型的操作逻辑 template<typename Callable> void invoke_handler(void* data) { (*static_cast<Callable*>(data))(); } template<typename Callable> void destroy_handler(void* data) { delete static_cast<Callable*>(data); } // 包装器类 class SimpleFunction { private: ErasedWrapper wrapper_; public: // 构造函数:接受任意可调用对象 template<typename Callable> SimpleFunction(Callable&& func) { wrapper_.data = new Callable(std::forward<Callable>(func)); // 绑定操作码对应的处理函数 wrapper_.handlers[static_cast<int>(OpCode::Invoke)] = &invoke_handler<Callable>; wrapper_.handlers[static_cast<int>(OpCode::Destroy)] = &destroy_handler<Callable>; } // 重载调用运算符:通过switch分发操作 void operator()() { switch (OpCode::Invoke) { case OpCode::Invoke: wrapper_.handlers[static_cast<int>(OpCode::Invoke)](wrapper_.data); break; default: break; } } // 析构函数:销毁底层对象 ~SimpleFunction() { wrapper_.handlers[static_cast<int>(OpCode::Destroy)](wrapper_.data); } }; // 测试代码 int main() { // 包装lambda表达式 SimpleFunction f1([](){ std::cout << "Hello from erased lambda!\n"; }); f1(); // 包装普通函数 void regular_func() { std::cout << "Hello from regular function!\n"; } SimpleFunction f2(regular_func); f2(); return 0; }
与std::function的关联
标准库中的std::function现在多采用小型对象优化+虚函数表的实现,但早期版本确实依赖这种"操作码+switch分发"的思路:把std::function支持的所有操作(调用、拷贝、移动、销毁等)都用操作码标记,通过switch或函数指针数组分发到具体类型的实现,从而完成对任意可调用对象的类型擦除包装。
内容的提问来源于stack exchange,提问作者Enlico
相关产品推荐
相关产品推荐

