如何将Lambda与实现移动语义的临时对象配合使用?
问题背景与需求
我想写一个能接收临时对象的Lambda,这个Lambda会被缓存起来延迟执行,而且执行时原对象已经销毁,所以必须通过移动而非拷贝来处理对象。核心需求如下:
- 定义类
X,实现移动语义,同时禁用拷贝构造函数和拷贝赋值运算符; - 定义函数
foo(X x),接收X类型对象; - 创建Lambda并存储为
std::function<void()>类型的变量f,用于延迟执行; - 在原
x销毁后执行f。
我尝试写的Lambda无法正常运行,虽然知道用对象指针能轻松解决,但想搞清楚:在正确实现移动语义的前提下,能不能直接用对象来实现这个需求?
以下是我写的无法正常运行的示例代码:
#include <iostream> #include <string> #include <functional> class X { private: std::string m_s; public: X(){} X(X&& other) { m_s = std::move(other.m_s);} ~X() { m_s = "Object destructed...";} X& operator=(X&& other) { m_s = std::move(other.m_s); return *this;} void SetString(const char* s) { m_s = s;} void Print() const { ::printf("X::Print() - String value: '%s'\n", m_s.c_str());} private: X(const X&) = delete; X& operator=(const X&) = delete; }; void foo(X x) { x.Print();} // Predefined lambda: auto l = [](X x){ foo(std::move(x));}; int main() { std::function<void()> f = nullptr; // Create temporary scope for 'x' to verify object // destruction happens before 'f' is called: { X x; x.SetString("Hello World!"); f = [&](){ l(std::move(x));}; } // Expecting to see 'Hello World!' f(); return 0; }
问题分析与解决方法
你的代码问题出在Lambda捕获的是原对象x的引用,当原对象所在的作用域结束后,这个引用就成了悬垂引用——指向的对象已经被销毁,此时再移动这个引用指向的对象,属于未定义行为,自然无法得到预期结果。
答案是肯定的:完全可以通过移动语义直接用对象实现需求,核心是在原对象销毁前,把它的所有权转移到Lambda的捕获列表中,让Lambda持有这个对象的所有权,这样原对象销毁后,Lambda里的对象依然有效。
修正后的代码
#include <iostream> #include <string> #include <functional> class X { private: std::string m_s; public: X(){} // 移动构造函数加上noexcept,符合标准库对可移动对象的要求 X(X&& other) noexcept : m_s(std::move(other.m_s)) {} ~X() { // 只在字符串非空时修改,避免覆盖已移动后的空字符串,方便观察生命周期 if (!m_s.empty()) m_s = "Object destructed..."; } // 移动赋值运算符同样加noexcept X& operator=(X&& other) noexcept { if (this != &other) m_s = std::move(other.m_s); return *this; } void SetString(const char* s) { m_s = s;} void Print() const { ::printf("X::Print() - String value: '%s'\n", m_s.c_str());} private: X(const X&) = delete; X& operator=(const X&) = delete; }; void foo(X x) { x.Print();} // Predefined lambda: auto l = [](X x){ foo(std::move(x));}; int main() { std::function<void()> f = nullptr; { X x; x.SetString("Hello World!"); // 关键:用std::move把x的所有权转移到Lambda的捕获变量中 // 加mutable是因为Lambda内部需要移动捕获到的x,默认Lambda是const的 f = [x = std::move(x)]() mutable { l(std::move(x)); }; } // 此时执行f,Lambda内部的x是有效对象 f(); return 0; }
关键细节说明
- 移动捕获:把原来的
[&]引用捕获改成[x = std::move(x)],这是C++14引入的移动捕获语法,直接将原对象的所有权转移到Lambda内部,原对象销毁后,Lambda里的x是独立的有效对象。 - mutable关键字:默认情况下,Lambda的捕获变量是
const的,而移动操作需要修改对象(把对象的资源转移出去),所以必须加mutable才能允许修改捕获的变量。 - noexcept修饰移动操作:标准库的很多组件(比如
std::function)在处理可移动对象时,会优先选择标记为noexcept的移动操作,避免在异常情况下 fallback 到拷贝(而你的类已经禁用了拷贝,所以必须加noexcept才能确保std::function正确处理)。 - 析构函数优化:原析构函数不管对象是否被移动,都会把
m_s改成固定字符串,这会导致移动后的空字符串被覆盖,不利于观察对象状态。修改后只在m_s非空时赋值,能更清晰地看到原对象销毁和Lambda内对象的状态。
运行修正后的代码,就能正确输出X::Print() - String value: 'Hello World!',完全通过移动语义实现了需求,不需要使用指针。
内容的提问来源于stack exchange,提问作者PazO
相关产品推荐
相关产品推荐

