当前位置: 首页 > article >正文

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";
});

 

 

 

 


http://www.kler.cn/a/386430.html

相关文章:

  • Nuxt.js 应用中的 schema:beforeWrite 事件钩子详解
  • 3D编辑器教程:如何实现3D模型多材质定制效果?
  • Ubuntu配置阿里云docker apt源
  • LeetCode 86.分隔链表
  • OCR识别铁路电子客票
  • Redis - 集群(Cluster)
  • LINUX离线安装Milvus
  • C#-类:成员属性
  • 【日志】392.判断子序列
  • 基于SSM+VUE儿童接种疫苗预约管理系统JAVA|VUE|Springboot计算机毕业设计源代码+数据库+LW文档+开题报告+答辩稿+部署教+代码讲解
  • 家庭宽带如何开启公网ipv4和ipv6
  • 基于SpringBoot的城镇住房保障系统性能优化
  • 设计模式-七个基本原则之一-开闭原则 + SpringBoot案例
  • Leetcode 同构字符串
  • 美团代付微信小程序系统 read.php 任意文件读取漏洞复现
  • # SpringMVC学习
  • nginx代理出现的请求头中获取不到acc_token问题
  • 从零开始训练一个大语言模型需要多少天?
  • Python学习从0到1 day26 第三阶段 Spark ① 数据输入
  • 论文阅读(三十五):Boundary-guided network for camouflaged object detection
  • 设置JAVA以适配华为2288HV2服务器的KVM控制台
  • 游戏中Dubbo类的RPC设计时的注意要点
  • 2024系统架构师---上午综合题真题(重复考试知识难点)
  • 【LeetCode】【算法】279. 完全平方数
  • 【GeoJSON在线编辑平台】(1)创建地图+要素绘制+折点编辑+拖拽移动
  • 图像格式中的 stride 和 pix stide