C++中lambda绑定std::function时拷贝与移动构造异常触发问题
为什么std::function<Test(Test)>调用lambda会同时触发拷贝和移动构造?
在C++中,将lambda赋值给std::function<Test(Test)>并调用时,会同时触发Test的拷贝构造函数和移动构造函数;但用auto声明lambda或调用普通函数时,只会触发预期的拷贝构造。以下是复现代码与输出:
复现代码
#include <iostream> #include <iomanip> #include <functional> using namespace std; struct Test { inline Test() { cout << setw(20) << "constructor "; PrintAddress(); } Test(const Test&) { cout << setw(20) << "copy constructor "; PrintAddress(); } Test& operator=(const Test&) { cout << setw(20) << "copy assignment "; PrintAddress(); return *this; } Test(Test&& other) { cout << setw(20) << "move constructor "; PrintAddress(); } Test& operator=(Test&&) { cout << setw(20) << "move assignment "; PrintAddress(); return *this; } virtual ~Test() { cout << setw(20) << "destructor "; PrintAddress(); } void PrintAddress() { cout << "Test&: " << this << endl; } }; Test regular_test (Test t) { cout << "regular_test" << endl; return t; } int main() { cout << "start" << endl; function<Test(Test)> lambda_test = [] (Test t) { cout << "lambda_test" << endl; return t; }; Test t; lambda_test(t); //regular_test(t); cout << "done" << endl; return 0; }
运行输出
start constructor Test&: 0x7fffef6faf28 copy constructor Test&: 0x7fffef6faf08 move constructor Test&: 0x7fffef6fade8 lambda_test move constructor Test&: 0x7fffef6faf10 destructor Test&: 0x7fffef6fade8 destructor Test&: 0x7fffef6faf10 destructor Test&: 0x7fffef6faf08 done destructor Test&: 0x7fffef6faf28
原因分析
1. std::function的参数转发逻辑导致额外移动
std::function的operator()在传递参数给内部存储的可调用对象时,会对参数执行右值转换(通常通过std::forward实现)。调用lambda_test(t)时:
- 首先,左值
t被拷贝构造为std::function::operator()的局部参数(对应输出中的copy constructor)。 - 接着,
std::function将这个局部参数以右值形式传递给lambda的形参,触发移动构造(对应move constructor)。
而直接调用auto声明的lambda或普通函数时,参数直接按左值传递,只会触发一次拷贝构造。
2. 返回值优化(RVO)的差异
- 普通函数
regular_test中,编译器可以执行命名返回值优化(NRVO):直接将函数形参t构造到调用者的返回对象内存中,避免额外的拷贝或移动操作。 - 对于
std::function,由于类型擦除的特性,std::function::operator()无法感知内部lambda的返回值构造逻辑,无法执行NRVO。lambda返回t时,会先将形参移动构造为临时对象(对应输出中第二次move constructor),再将该临时对象作为std::function的返回值,最终被销毁。
补充验证
- 若改用
auto声明lambda并直接调用,输出只会有一次拷贝构造,返回时无额外移动,与普通函数行为一致。 - 若关闭编译器优化(如添加
-fno-elide-constructors参数),普通函数也会触发移动构造,证明NRVO是普通函数无额外移动的核心原因。
内容的提问来源于stack exchange,提问作者David Carpenter
相关产品推荐
相关产品推荐

