static_function实现中复制消除失效的原因排查
静态函数对象static_function复制消除失效问题排查
我尝试实现一款采用预分配存储的「静态」尺寸函数static_function,区别于使用动态堆分配的std::function。但该实现未达预期,callable的复制消除并未像单独使用lambda时生效。
以下是-O3编译选项下的预期与实际行为对比代码及输出:
static_function实现代码
#include <utility> #include <cstddef> #include <type_traits> template <typename T, size_t StackSize = 64> class static_function; // TODO: move and swap // - can move smaller instance to larger instance // - only instances of the same size are swappable // TODO: condiotnal dynamic storage? template <typename Ret, typename ... Args, size_t StackSize> class static_function<Ret(Args...), StackSize> { public: constexpr static size_t static_size = StackSize; using return_type = Ret; template <typename Callable> constexpr explicit static_function(Callable &&callable) : pVTable_(std::addressof(v_table::template get<Callable>())) { static_assert(sizeof(std::decay_t<Callable>) <= static_size, "Callable type is too big!"); new (&data_) std::decay_t<Callable>(std::forward<Callable>(callable)); } constexpr return_type operator()(Args ... args) const { return (*pVTable_)(data_, std::move(args)...); } ~static_function() noexcept { pVTable_->destroy(data_); } private: using stack_data = std::aligned_storage_t<static_size>; struct v_table { virtual return_type operator()(const stack_data&, Args &&...) const = 0; virtual void destroy(const stack_data&) const = 0; template <typename Callable> static const v_table& get() { struct : v_table { return_type operator()(const stack_data &data, Args &&... args) const override { return (*reinterpret_cast<const Callable*>(&data))(std::move(args)...); } void destroy(const stack_data &data) const override { reinterpret_cast<const Callable*>(&data)->~Callable(); } } constexpr static vTable_{}; return vTable_; } }; private: stack_data data_; const v_table *pVTable_; };
测试代码
#include <iostream> #include <string> int main() { struct prisoner { std::string name; ~prisoner() { if (!name.empty()) std::cout << name << " has been executed\n"; } }; std::cout << "Expected:\n"; { const auto &func = [captured = prisoner{"Pvt Ryan"}](int a, int b) -> std::string { std::cout << captured.name << " has been captured!\n"; return std::string() + "oceanic " + std::to_string(a + b); }; std::cout << func(4, 811) << '\n'; } std::cout << "THE END\n\n"; std::cout << "Actual:\n"; { const auto &func = static_function<std::string(int, int)>([captured = prisoner{"Pvt Ryan"}](int a, int b) -> std::string { std::cout << captured.name << " has been captured!\n"; return std::string() + "oceanic " + std::to_string(a + b); }); std::cout << func(4, 811) << '\n'; } std::cout << "THE END!\n"; return 0; }
输出结果
Expected: Pvt Ryan has been captured! oceanic 815 Pvt Ryan has been executed THE END Actual: Pvt Ryan has been executed Pvt Ryan has been captured! oceanic 815 Pvt Ryan has been executed THE END!
问题原因与修复方案
核心问题
你的实现存在两个关键问题:
- 临时lambda被复制而非移动:传入
static_function构造函数的临时lambda被复制到预分配存储区,导致临时lambda先析构(输出第一次"执行"信息),static_function销毁时内部存储的lambda再析构(第二次输出),出现额外的析构调用。 - 析构调用存在未定义行为:
v_table的destroy函数通过const指针调用析构函数,而存储区的对象本身是非const的,属于未定义行为。
修复步骤
1. 改用移动构造减少复制
修改static_function的构造函数,确保优先使用移动构造转移lambda资源:
template <typename Callable> constexpr explicit static_function(Callable &&callable) : pVTable_(std::addressof(v_table::template get<std::decay_t<Callable>>())) { static_assert(sizeof(std::decay_t<Callable>) <= static_size, "Callable type is too big!"); // 使用std::move触发移动构造(针对右值参数) new (&data_) std::decay_t<Callable>(std::move(callable)); }
注:这里用std::move替代std::forward,因为我们明确要把传入的右值lambda移动到存储区,避免不必要的复制。
2. 修复析构函数的未定义行为
修改v_table的虚函数定义,移除stack_data的const限定,并调整指针类型:
struct v_table { virtual return_type operator()(const stack_data&, Args &&...) const = 0; // 移除destroy参数的const限定 virtual void destroy(stack_data&) const = 0; template <typename Callable> static const v_table& get() { struct : v_table { return_type operator()(const stack_data &data, Args &&... args) const override { return (*reinterpret_cast<const Callable*>(&data))(std::move(args)...); } // 接收非const的stack_data,使用非const指针调用析构 void destroy(stack_data &data) const override { reinterpret_cast<Callable*>(&data)->~Callable(); } } constexpr static vTable_{}; return vTable_; } };
同时更新static_function的析构函数,传入非const的data_:
~static_function() noexcept { pVTable_->destroy(data_); }
3. 额外优化:启用复制消除
修改后,编译器在-O3下可以优化掉移动操作(如果lambda的移动构造是 trivial 的),最终只会保留一次析构调用,和预期行为一致。
内容的提问来源于stack exchange,提问作者Sergey Kolesnik
相关产品推荐
相关产品推荐

