C++移动语义和lambda表达式
一、移动语义
1.右值引用
遵循规则:
- 右值引用只能绑定到右值,包括纯右值(如字面量、表达式结果)和将亡值(如函数返回的临时对象)。
- 有名称的右值引用被视为左值。此时该右值的生命周期与对应的右值引用的生命周期一样。
int func() {
return 0;
}
int a = 1, b = 2;
int&& r1 = 10;
int&& r2 = a + b;
int&& r3 = func();
右值引用主要用于移动语义和完美转发。
2.移动语义
移动语义允许将一个对象的资源或内容转移到另一个对象,从而避免不必要的复制。
移动语义通过右值引用来实现,其核心是移动构造函数和移动赋值运算符。
①移动构造函数和移动赋值运算符:
移动构造函数是一个特殊的构造函数,它的参数类型为右值引用。当一个右值被用来初始化一个新对象时,会调用移动构造函数。
移动赋值运算符是一个特殊的赋值运算符,其参数同样是右值引用。当一个已存在的对象需要从另一个对象接收资源时,会调用移动赋值运算符。
②特点:
- 使用移动构造函数和移动赋值运算符会进行资源的转移,这样可以提高效率,尤其是在处理大型对象或资源密集型操作时。
- 如果没有显示定义,编译器将会提供默认的移动构造函数和移动赋值运算符。
- 移动构造函数和移动赋值运算符通常会将原对象设置为无效状态(例如将指针设置为nullptr),以避免资源的双重释放或无效使用。
③示例:
class String {
private:
char* data;
int size;
public:
String(const char* s = "") {
data = new char[strlen(s) + 1];
size = strlen(s);
strcpy(data,s);
}
String(String&& other) noexcept {
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
String& operator=(String&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
~String() {
delete[] data;
}
void print() {
if (data == nullptr)
cout << "nullptr" << endl;
else
cout << this->data << endl;
}
};
String str1("Hello World!");
//移动构造函数
String str2(move(str1));
cout << "str1:";
str1.print();
cout << "str2:";
str2.print();
//移动赋值运算符
String str3;
str3 = move(str2);
cout << "str2:";
str2.print();
cout << "str3:";
str3.print();
//输出
str1:nullptr
str2:Hello World!
str2:nullptr
str3:Hello World!
注意:资源转移并不等于析构对象。资源转移后原对象仍然存在,通常会进入一种有效但未定义的状态。原对象不再拥有这些资源,因此不应再次使用,直到资源被重新分配或对象被销毁。
3.std::move
std::move通常用于用于将其参数转换为右值引用。
当std::move用于左值时,可以将其暂时转换为右值引用。因此可以通过这种方式调用移动构造函数或移动赋值运算符。
vector<int> v1 = { 1,2,3 };
vector<int> v2 = move(v1);
for (auto x : v2) {
cout << x << " ";
}
cout << endl;
//输出
1 2 3
特点:
- std::move本身不执行任何移动操作,资源移动由移动构造函数或移动赋值运算符来完成。
- 使用std::move可以避免对象在转移时进行不必要的拷贝操作,从提高程序的运行效率,尤其是在处理大型数据结构时。
- 当需要转移对象的所有权时,可以使用std::move。例如将一个临时变量转移给另一个变量,或者在函数返回时用它来转移对象的所有权。
4.完美转发
在函数模板将参数转发给其它函数时,使用完美转发可以保证参数的值类别(左值或右值)不会发生改变。
①万能引用:
万能引用是一种特殊的引用类型,它既可能是左值引用,也可能是右值引用。
- 只有当T或auto后紧跟&&时才可能是万能引用。
- 只有当类型需要推导时才是万能引用。
- const auto &&和const T&&不需要推导,是右值引用。
②引用折叠:
引用折叠是指在模板进行参数推导过程中,如果出现引用的引用,编译器会根据相应的规则将这些引用类型进行简化和折叠。
- T& &、T& &&、T&& &折叠成T&。
- T&& &&折叠成T&&。
③std::forward:
std::forward能够根据传递的参数类型,保持该参数的值类别。如果一个参数是右值,处理后还是右值;如果是左值,处理后还是左值。
④示例:
void func(int& x) {
cout << "Left value:" << x << endl;
}
void func(int&& x) {
cout << "Right value:" << x << endl;
}
template<typename T>
void print(T&& arg) {
func(forward<T>(arg));
}
int x = 10;
print(x);
print(move(x));
//输出
Left value:10
Right value:10
完美转发只能在函数模板中通过万能引用实现,配合std::forward使用可以保持参数原始的值类别。
二、lambda表达式
1.概念
Lambda表达式允许定义匿名函数,使得代码更加灵活和简洁。
Lambda表达式可以直接在需要调用函数的地方定义,可以被直接调用或作为回调函数使用,适用于STL算法、多线程、QT等。
示例:
void CallBack(const function<void()>& callback) {
callback();
}
auto Lambda = []() {
cout << "Lambda" << endl;
};
CallBack(Lambda);
2.语法
①Lambda表达式的基本语法形式如下:
[capture_list] (parameters) mutable -> return_type { function_body }
- capture_list:捕获列表,可以捕获所属作用域中的变量。
- parameters:参数列表,用于将参数传递给Lambda表达式。
- mutable:可选关键字,允许Lambda表达式修改捕获的变量。
- return_type:返回类型,如果省略,编译器会自动推导。
- function_body:主体部分,用于代码实现。
②注意事项:
- 捕获列表[]是Lambda表达式的标志,不能省略。
- 如果不需要传递参数,参数列表()可以省略。
- 使用mutable时参数列表()不能省略。
- 默认情况下Lambda表达式是const函数,使用mutable可以取消其常量性。
vector<int> v = { 3,2,5,1,4 };
sort(v.begin(), v.end(), [](int a, int b) {return a > b; });
for (auto x : v) {
cout << x << " ";
}
cout << endl;
//输出
5 4 3 2 1
3.捕获列表
- []:不捕获任何变量。
- [=]:以值传递的方式捕获所有变量。
- [&]:以引用传递的方式捕获所有变量。
- [var]:以值传递的方式捕获var。
- [&var]:以引用传递的方式捕获var。
- [=, &var]:var按引用捕获,其它变量按值捕获。
- [&, var]:var按值捕获,其它变量按引用捕获。
- [this]:捕获当前对象,本质上是复制指针。
class MyClass {
public:
void sayHello() {
cout << "Hello World!" << endl;
}
void print() {
auto Lambda = [this] {
this->sayHello();
};
Lambda();
}
};
注意:捕捉列表不允许变量重复传递。
例如[=, var]:已经以值传递的方式捕捉了所有变量,但又捕获一次var,这是错误的行为。
4.应用
①作为函数指针使用:
Lambda表达式可以被转换为函数指针,前提是它不捕获任何外部变量。
auto sum = [](int a, int b) {
return a + b;
};
int (*fptr)(int, int) = sum;
cout << fptr(2, 3) << endl; //5
②在QT中的应用:
connect(button, &QPushButton::clicked, this, []() {
qDebug() << "Lambda";
});