如何在延迟调用模板中条件转换std::forward参数为char*(SFINAE)
问题分析
实现类成员函数延迟调用工具NotifyAll/NotifyAllNow时,遇到char*参数(如std::string::c_str())的生命周期问题:原字符串内存释放后,延迟调用触发时会出现未定义行为。虽然设计了LifeExtender类持有字符串副本以延长生命周期,但在std::invoke环节无法自动将LifeExtender对象转换为char*,同时还要保证其他参数的正常转发。尝试基于std::enable_if的identity函数编译失败,临时方案存在过多拷贝问题,且不能修改目标成员函数的char*参数类型。
解决方案
核心思路是通过两个辅助逻辑实现:
- 自动包装
char*参数:在创建延迟调用对象时,自动将const char*类型的参数包装为LifeExtender,其他参数保持原类型并完美转发。 - 参数转发时的类型适配:在触发调用时,对
LifeExtender类型的参数自动转换为const char*,其他参数直接转发。
1. 实现LifeExtender类
首先定义持有字符串副本的辅助类,提供到const char*的隐式转换:
#include <string> #include <functional> #include <tuple> #include <type_traits> #include <cstdio> class LifeExtender { private: std::string str_; public: explicit LifeExtender(const char* s) : str_(s) {} // 隐式转换为const char*,供目标函数调用时使用 operator const char*() const noexcept { return str_.c_str(); } };
2. 参数包装与转发的辅助函数
wrap_arg:自动识别const char*参数并包装为LifeExtender,其他参数直接转发。forward_arg:调用时将LifeExtender转换为const char*,其他参数保持完美转发。
// 判断是否为const char*类型(忽略cv限定) template <typename T> struct is_const_char_ptr : std::is_same<std::remove_cv_t<T>, const char*> {}; // 包装参数:const char*转为LifeExtender,其他类型直接转发 template <typename T> auto wrap_arg(T&& arg) { if constexpr (is_const_char_ptr<std::decay_t<T>>::value) { return LifeExtender(std::forward<T>(arg)); } else { return std::forward<T>(arg); } } // 转发参数:LifeExtender转为const char*,其他类型完美转发 template <typename T> std::enable_if_t<!std::is_same_v<std::decay_t<T>, LifeExtender>, T&&> forward_arg(T&& arg) noexcept { return std::forward<T>(arg); } inline const char* forward_arg(LifeExtender&& le) noexcept { return static_cast<const char*>(le); } inline const char* forward_arg(const LifeExtender& le) noexcept { return static_cast<const char*>(le); }
3. 延迟调用对象的实现
重新实现DelayedCall模板类,利用上述辅助函数处理参数:
template <typename Class, typename... Args> class DelayedCall { private: Class* obj_; void (Class::*func_)(Args...); // 保存包装后的参数tuple std::tuple<decltype(wrap_arg(std::declval<Args>()))...> args_; // 递归展开tuple并调用目标函数 template <std::size_t... I> void invoke_impl(std::index_sequence<I...>) { (obj_->*func_)(forward_arg(std::get<I>(std::move(args_)))...); } public: // 构造函数:接收对象指针、成员函数指针和原始参数,自动包装参数 DelayedCall(Class* obj, void (Class::*func)(Args...), Args&&... args) : obj_(obj), func_(func), args_(wrap_arg(std::forward<Args>(args))...) {} // 触发延迟调用 void operator()() { invoke_impl(std::make_index_sequence<sizeof...(Args)>{}); } }; // NotifyAll函数:创建延迟调用对象 template <typename Class, typename... Args> auto NotifyAll(Class* obj, void (Class::*func)(Args...), Args&&... args) { return DelayedCall<Class, Args...>(obj, func, std::forward<Args>(args)...); }
4. 测试示例
class TestClass { public: void print(const char* msg, int num) { printf("Msg: %s, Num: %d\n", msg, num); } }; int main() { TestClass obj; // 传入临时std::string的c_str(),原字符串会立即销毁 auto delayed = NotifyAll(&obj, &TestClass::print, std::string("Hello World").c_str(), 42); // 触发延迟调用,此时LifeExtender仍持有字符串副本 delayed(); return 0; }
方案优势
- 自动处理生命周期:无需手动传入
LifeExtender,所有const char*参数都会被自动包装,避免内存访问问题。 - 完美转发:非
char*参数通过std::forward实现完美转发,避免不必要的拷贝。 - 无侵入性:不需要修改目标成员函数的参数类型,完全兼容原有接口。
内容的提问来源于stack exchange,提问作者Gizmo
相关产品推荐
相关产品推荐

