如何编写可从lambda构造的仿函数并提取其中的this指针?
自定义仿函数实现小对象优化并存储捕获的this指针
核心思路
std::function的小对象优化依赖内部固定大小缓冲区存储小型可调用对象(比如仅捕获this的lambda,大小通常等于指针长度),以此避免堆分配。自定义仿函数要实现类似逻辑,核心是:
- 用栈上固定大小缓冲区存储捕获
this的lambda(或直接存储this指针+调用逻辑) - 通过类型擦除实现统一调用接口,同时支持提取存储的
this指针
具体实现步骤
1. 设计仿函数内存布局
自定义仿函数需要包含两部分核心成员:
- 对齐的固定大小缓冲区:尺寸参考std::function的小对象缓冲区(通常24字节),用于容纳小型可调用对象
- 函数指针:分别负责调用可调用对象和销毁它,实现类型擦除
2. 从捕获this的lambda构造
仅捕获this的lambda本质是编译器生成的匿名类,大小等于指针长度。我们可以直接将lambda对象拷贝到缓冲区中,无需堆分配。
3. 实现调用与this提取逻辑
通过存储的函数指针触发lambda调用;若要提取this,可直接访问lambda的内部成员(依赖编译器实现),或在构造时显式提取this单独存储。
代码示例
版本1:存储完整lambda(依赖编译器实现)
#include <cstddef> #include <type_traits> #include <utility> #include <cstdio> // 自定义仿函数,支持小对象优化 class MyFunction { public: MyFunction() noexcept : invoke_(nullptr), destroy_(nullptr) {} // 从捕获this的lambda构造 template<typename F> MyFunction(F&& f) noexcept(std::is_nothrow_move_constructible_v<F>) { static_assert(sizeof(F) <= sizeof(buffer_), "Callable exceeds small object buffer size"); static_assert(alignof(F) <= alignof(decltype(buffer_)), "Callable alignment requirement not met"); // 将lambda拷贝到缓冲区 new (buffer_) F(std::forward<F>(f)); // 设置调用和销毁逻辑 invoke_ = [](void* buf) { (*static_cast<F*>(buf))(); }; destroy_ = [](void* buf) { static_cast<F*>(buf)->~F(); }; } ~MyFunction() noexcept { if (destroy_) destroy_(buffer_); } void operator()() const { if (invoke_) invoke_(buffer_); } // 提取存储的this指针(依赖GCC/Clang的lambda内部成员命名) template<typename T> T* get_this() const noexcept { auto* lambda = static_cast<std::decay_t<decltype(*this)>::LambdaType*>(buffer_); return lambda->__this; } private: alignas(std::max_align_t) char buffer_[24]; void (*invoke_)(void*); void (*destroy_)(void*); // 辅助类型推导 template<typename T> struct LambdaTypeHelper; template<typename C, typename R> struct LambdaTypeHelper<R(C::*)()> { using type = C; }; }; // 测试 class TestClass { public: void func() { auto lambda = [this]() { printf("Lambda called, this = %p\n", this); }; MyFunction func_obj(lambda); func_obj(); TestClass* extracted_this = func_obj.get_this<TestClass>(); printf("Extracted this = %p\n", extracted_this); } }; int main() { TestClass tc; tc.func(); return 0; }
版本2:直接存储this与调用逻辑(可移植性更高)
如果不想依赖编译器的lambda内部实现,可以直接存储this指针和成员函数指针:
#include <cstdio> class SimplifiedMyFunction { public: template<typename C> SimplifiedMyFunction(C* obj, void (C::*func)()) noexcept : obj_(obj), func_([](void* obj) { auto* typed_obj = static_cast<C*>(obj); (typed_obj->*func)(); }) {} void operator()() const { func_(obj_); } template<typename C> C* get_this() const noexcept { return static_cast<C*>(obj_); } private: void* obj_; void (*func_)(void*); }; // 测试 class TestClass { public: void func() { SimplifiedMyFunction func_obj(this, &TestClass::impl); func_obj(); printf("Extracted this = %p\n", func_obj.get_this<TestClass>()); } private: void impl() { printf("Impl called, this = %p\n", this); } }; int main() { TestClass tc; tc.func(); return 0; }
关键注意事项
- 缓冲区对齐与大小:用
alignas(std::max_align_t)保证缓冲区兼容所有基础类型的对齐要求,大小可根据目标平台调整(比如64位系统下24字节足够容纳多数小型可调用对象) - 可移植性:版本1依赖编译器对lambda的内部命名规则,版本2通过显式存储
this和成员函数指针,兼容性更强 - 资源安全:务必在析构函数中销毁缓冲区中的lambda对象,避免内存泄漏
内容的提问来源于stack exchange,提问作者glades
相关产品推荐
相关产品推荐

