按值捕获和按引用捕获在性能上的不同
- 按值捕获:
- 性能特点:按值捕获会复制被捕获的变量到Lambda表达式内部。如果被捕获的变量是较大的对象,复制操作会带来额外的性能开销,包括时间开销(执行复制操作)和空间开销(为复制的对象分配内存)。
- 举例:
#include <iostream>
#include <vector>
class BigObject {
public:
std::vector<int> data;
BigObject() {
for (int i = 0; i < 1000000; ++i) {
data.push_back(i);
}
}
};
int main() {
BigObject obj;
auto lambdaByValue = [obj]() {
// 这里使用按值捕获的obj
std::cout << "Using obj by value capture. Size: " << obj.data.size() << std::endl;
};
lambdaByValue();
return 0;
}
- 在这个例子中,
BigObject
对象很大,按值捕获obj
时会复制整个obj
,包括其包含的std::vector<int>
,这会消耗较多的时间和空间。
- 按引用捕获:
- 性能特点:按引用捕获不会复制被捕获的变量,而是在Lambda表达式内部使用原始变量的引用。因此,它没有复制带来的性能开销,无论是时间还是空间上都相对高效,尤其对于大对象。
- 举例:
#include <iostream>
#include <vector>
class BigObject {
public:
std::vector<int> data;
BigObject() {
for (int i = 0; i < 1000000; ++i) {
data.push_back(i);
}
}
};
int main() {
BigObject obj;
auto lambdaByReference = [&obj]() {
// 这里使用按引用捕获的obj
std::cout << "Using obj by reference capture. Size: " << obj.data.size() << std::endl;
};
lambdaByReference();
return 0;
}
- 在这个例子中,按引用捕获
obj
,Lambda表达式内部使用的是obj
的引用,没有复制操作,性能更高。
选择捕获方式以优化性能的场景
- 优先选择按值捕获的场景:
- 场景一:需要独立副本:当Lambda表达式需要一个独立的变量副本,且不希望其修改影响到外部变量,同时被捕获的变量较小(如基本数据类型)时,按值捕获是较好的选择。例如:
#include <iostream>
int main() {
int num = 5;
auto lambda = [num]() {
num = 10; // 这里修改的是按值捕获的副本
std::cout << "Inside lambda: " << num << std::endl;
};
lambda();
std::cout << "Outside lambda: " << num << std::endl;
return 0;
}
- 场景二:防止悬空引用:如果Lambda表达式的生命周期可能比被捕获变量的生命周期长,使用按值捕获可以防止悬空引用问题。比如在多线程场景中,如果在一个线程中创建Lambda表达式并捕获局部变量,使用按值捕获可以确保在该线程执行Lambda表达式时,被捕获变量不会因为超出作用域而被销毁。
- 优先选择按引用捕获的场景:
- 场景一:大对象且不需要独立副本:当被捕获的对象较大,且Lambda表达式不需要独立副本,而是希望直接操作原始对象时,按引用捕获可以显著提高性能。例如上述
BigObject
的例子。
- 场景二:频繁调用且对象状态变化:如果Lambda表达式会被频繁调用,且每次调用都依赖外部变量的最新状态,按引用捕获可以避免每次都复制变量,从而提升性能。例如在一个循环中频繁调用Lambda表达式,且该表达式依赖外部的一个大对象的最新状态。