【C++11】初识——右值引用和移动语义,可变参数模板

🌈个人主页 :@ꪔ小林Y

✨个人专栏 :《C++》,《C语言》,《数据结构入门------从原理到实战》,《拾光Linux》,《网络安全》

🍀代码信条 :每一行代码都是成长的脚印👣,每一次调试成功都是对坚持的回应

目录

【C++11】1

1.右值引用和移动语义

C++11之前学的引用是左值引用,C++11新增了右值引用语法特性,无论是左值引用还是右值引用,都是给对象取别名。

1.1左值和右值

  • 左值(lvalue) :一个表示数据的表达式,存储在内存中,可以获取它的地址,左值可以出现在赋值符号的左边,也可以在右边。定义时const修饰符后的左值,不能给它赋值,但可以取它的地址。如存储在内存中,有明确存储地址可以取地址的对象。
cpp 复制代码
int main()
{
	//左值:可以取地址
	//以下的p,b,c,*p,s,s[0]就是常见的左值
	int* p = new int(0);
	int b = 1;
	const int c = b;
	*p= 10;
	string s("11111");
	s[0] = 'x';
}
  • 右值(rvalue) :是一个数据的表达式,要么是字面值常量,要么是表达式求值过程中创建的临时对象等,右值可以出现在赋值符号的右边,但是不能出现在赋值符号的左边,右值不能取地址。如临时变量,字面量常量,存储于寄存器中的变量等。
cpp 复制代码
//右值:不能取地址
double x = 1.1, y = 2.2;
//以下10,x+y,fmin(x,y),string("11111")都是常见的右值
10;
x + y;
fmin(x, y);
string("11111");

1.2.左值引用和右值引用

  • 左值引用就是给左值取别名,例:int& r1=x;,右值引用同样的道理,例:int&& rr1=y;
  • 左值引用不能直接引用右值,但是const左值可以引用右值 ,例:const int& rx1=10;
  • 右值引用不能直接引用左值,但是右值引用可以引用move(左值) ,例:int && rrx1=move(b);
  • 注意,变量表达式都是左值属性 ,也就意味着一个右值被右值引用绑定后,右值引用变量变量表达式的属性是左值。
  • 左值引用解决的效率问题:传左值引用传参,减少拷贝;传左值引用返回,减少拷贝(部分场景)

左值和右值的参数匹配:C++11以后,分别重载左值引用、const左值引用、右值引用作为形参的f函数,那么实参是左值会匹配f(左值引用),实参是const左值会匹配f(const 左值引用),实参是右值会匹配f(右值引用)。

1.3引用延长生命周期

右值引用可用于为临时对象延长生命周期,const 的左值引用也能延长临时对象生存期,但这些对象无法被修改。

1.4移动构造和移动赋值

  • 移动构造函数是⼀种构造函数 ,类似拷贝构造函数,移动构造函数要求第⼀个参数是该类类型的引用 ,但是不同的是要求这个参数是右值引用 ,如果还有其他参数,额外的参数必须有缺省值。

  • 移动赋值是⼀个赋值运算符的重载 ,他跟拷贝赋值构成函数重载,类似拷贝赋值函数,移动赋值函数要求第⼀个参数是该类类型的引用,但是不同的是要求这个参数是右值引用。

  • 对于像string/vector这样的深拷贝的类或者包含深拷贝的成员变量的类 ,移动构造和移动赋值才有意义,因为移动构造和移动赋值的第一个参数都是右值引用的类,他的本质是要"窃取"引用的右值对象的资源,而不是像拷贝构造和拷贝赋值那样去拷贝资源,从提高效率。

1.5引用折叠

  • 通过模板或者typedef中的类型操作可以构成引用的引用。
  • 规则:右值引用的右值引用折叠成右值引用,左右其他组合均折叠成左值引用。
cpp 复制代码
int main()
{
	//int&& &r=i;这样直接引用是有问题的,会报错
	typedef int& lref;
	typedef int&& rref;
	int n = 0;
	//引用折叠
	lref& r1 = n;//r1的类型是int&
	lref&& r2 = n;//r2的类型是int&
	rref& r3 = n;//r3的类型是int&
	rref&& r4 = 1;//r4的类型是int&&
}
cpp 复制代码
//由于引用折叠限定,f1实例化以后总是一个左值引用
template<class T>
void f1(T& x)
{}
//例:
//1.没有折叠->实例化为void f1(int& x)
f1<int>(n);
f1<int>(0);//传右值会报错
//2.折叠->实例化为void f1(int& x)
f1<int&>(n);
f1<int&>(0);//传右值会报错
//3.折叠->实例化为void f1(int& x)
f1<int&&>(n);
f1<int&&>(0);//传右值会报错
//4.折叠->实例化为void f1(const int& x)
f1<const int&>(n);
f1<const int&>(0);
//5.折叠->实例化为void f1(const int& x)
f1<const int&&>(n);
f1<const int&&>(0);
cpp 复制代码
//由于引用折叠限定,f2实例化以后可以是左值引用,也可以是右值引用
template<class T>
void f2(T&& x)
{}
//例:
//1.没有折叠->实例化为void f2(int&& x)
f2<int>(n);//报错
f2<int>(0);
//2.折叠->实例化为void f2(int& x)
f2<int&>(n);
f2<int&>(0);//报错
//3.折叠->实例化为void f2(int&& x)
f2<int&&>(n);//报错
f2<int&&>(0);
  • 由此,引出了万能引用:
    传左值,实例化出左值引用的Function函数
    传右值,实例化右值引用的Function函数
cpp 复制代码
template <class T>
void Function(T&& t)
{
	int a=0;
	T x=1;
	cout<<&a<<endl;
	cout<<&x<<endl<<endl;
}

1.6完美转发

变量表达式都是左值 属性,也就意味着一个右值被右值引用绑定后 ,右值引用变量表达式的属性是左值,我们想要保持对象的属性,就需要使用完美转发实现。

bash 复制代码
template <class T> T&& forward (typename remove_reference<T>::type& arg);
bash 复制代码
template <class T> T&& forward (typename remove_reference<T>::type&& arg);
cpp 复制代码
template<class X>
void push_back(X&& x)
{
//完美转发是为了:
//保持x的属性往下传递
//实例化push_back(Lin::string&& x),保持右值属性往下传递
//实例化push_back(Lin::string& x),保持左值属性往下传递
	insert(end(),forward<X>(x));
}

2.可变参数模板

2.1概念理解

C++11支持可变数量参数的函数模板和类模板,可变数目的参数被称为参数包,存在两种参数包:模板参数包:template<class... Args>; ,表示零或多个模板参数;函数参数包:void Func(Args&&... args)(args为零或多个参数);:表示零或多个函数参数。通过...触发参数包展开的过程叫包扩展

  • template <class ...Args> void Func(Args... args) {}//传值形参
  • template <class ...Args> void Func(Args&... args) {}//传左值引用形参
  • template <class ...Args> void Func(Args&&... args) {}//传万能引用形参
    用省略号指出⼀个模板参数或函数参数的⼀个包 。函数参数包可以用左值引用或右值引用表示,每个参数实例化时遵循引用折叠规则。

2.2折叠表达式(c++17)

cpp 复制代码
template<class ...Args>
void Print(Args... args)
{
	((cout<<args<<" "),...);
	cout<<"\n";
}
int main()
{
	Print();
	Print(1);
	Print(1,string("xxxx"));
	Print(1,string("xxxxx"),2.2);
	return 0;
	}

2.3emplace接口

empalce系列的接口均为模板可变参数 ,功能上兼容push和insert系列,但是empalce还支持新玩法,假设容器为container,empalce还支持直接插入构造T对象的参数,这样有些场景会更高效⼀些,可以直接在容器空间上构造T对象。

bash 复制代码
template <class... Args> void emplace_back (Args&&... args);
bash 复制代码
template <class... Args> iterator emplace (const_iterator position,
Args&&... args);

empalce_back和push_back对比:

push_back:需先构造临时对象,再通过拷贝 / 移动构造到容器中;

emplace_back:接收构造对象的参数包,直接在容器节点中构造对象,减少一次拷贝 / 移动。

下面场景详细看一下区别

  • 相同: 例:

    构造pair+拷贝/移动构造pair到list节点中data上
  • 不同:例:
    直接把构造pair参数包往下传,直接用pair参数包构造pair,push_back做不到
    lt1.emplace_back("苹果",1);
    //lt1.push_back("苹果",1);错误,要传pair或者{}隐式转换pair的值 lt1.push_back({"苹果",1});

2.4模拟实现list的emplace_back

cpp 复制代码
#pragma once
namespace Lin
{
	template<class T>
	struct list_node
	{
		list_node<T>* _next;
		list_node<T>* _prev;
		T _data;
		list_node(T&& data)
			:_next(nullptr)
			, _prev(nullptr)
			, _data(move(data)) // 移动构造或拷贝构造
		{}
		template<class... Args>
		list_node(Args&&... args)
			: _next(nullptr)
			, _prev(nullptr)
			, _data(std::forward<Args>(args)...) 
		{}
	};
	template<class T, class Ref, class Ptr>
	struct list_iterator
	{
		using Self = list_iterator<T, Ref, Ptr>;
		using Node = list_node<T>;
		Node* _node;
		list_iterator(Node* node)
			:_node(node)
		{}
		// *it = 1
		Ref operator*() {// ......}
		Ptr operator->() {// ......}
		// ++it
		Self& operator++() {// ......}
		Self operator++(int) {// ......}
		// --it
		Self& operator--() {// ......}
		Self operator--(int) {// ......}
		bool operator!=(const Self& s) const {// ......}
		bool operator==(const Self& s) const {// ......}
	};
	template<class T>
	class list
	{
		using Node = list_node<T>;
	public:
		using iterator = list_iterator<T, T&, T*>;
		using const_iterator = list_iterator<T, const T&, const T*>;
		// using iterator = list_iterator<T>;
		// using const_iterator = list_const_iterator<T>;
		iterator begin() {// ......}
		iterator end() {// ......}
		const_iterator begin() const {// ......}
		const_iterator end() const {// ......}
		void empty_init() {// ......}
		list() {// ......}
		list(initializer_list<T> il) {// ......}
		template <class InputIterator>
		list(InputIterator first, InputIterator last) {// ......}
		list(size_t n, T val = T()) {// ......}
		list(int n, T val = T()) {// ......}
		~list() {// ......}
		// 传统写法
		// lt2(lt1)
		list(const list<T>& lt) {// ......}
		// lt1 = lt3;
		list<T>& operator=(const list<T>& lt) {// ......}
		void swap(list<T>& lt) {// ......}
		void clear() {// ......}
		void push_back(const T& x) {// ......}
		// 不是万能引用
		// 因为T是list的参数,list<Lotso::string>实例化时,T就确定了。
		void push_back(T&& x) {// ......}
		void push_front(const T& x) {// ......}
		void pop_back() {// ......}
		void pop_front() {// ......}
		void insert(iterator pos, const T& x) {// ......}
		void insert(iterator pos, T&& x) {// ......}
		iterator erase(iterator pos) {// ......}
		size_t size() const {// ......}
		template<class... Args>
		void emplace_back(Args&&... args)
		{
			insert(end(), std::forward<Args>(args)...);
		}
		// 原理:本质编译器根据可变参数模板生成对应参数的函数
		/*void emplace_back(string& s)
		{
			insert(end(), std::forward<string>(s));
		}
		void emplace_back(string&& s)
		{
			insert(end(), std::forward<string>(s));
		}
		void emplace_back(const char* s)
		{
			insert(end(), std::forward<const char*>(s));
		}
		*/
		template<class... Args>
		iterator insert(iterator pos,Args&&... args)
		{
			Node* cur = pos._node;
			Node* newnode = new Node(std::forward<Args>(args)...);
			Node* prev = cur->_prev;
			// prev newnode cur
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
			return iterator(newnode);
		}

	private:
		Node* _head;
		size_t _size = 0;
	};
}

🎊到这儿,本期【C++11】初识------右值引用和移动语义,可变参数模板的内容就结束了。如果文中有表述不准的地方,或是你有更清晰的理解思路,强烈欢迎在评论区留言交流------ 技术路上多碰撞,才能更快进步

觉得内容对你有帮助的话,别忘了点赞❤️➕收藏🌟,方便后续回顾复习;想跟着一起系统学习数据结构的朋友,也可以点击关注,下一期我们会聚焦更进一步的学习。不见不散✌️

相关推荐
要练八块腹肌1 小时前
红黑树的实现以及对map和set的封装
c++
骑着蜗牛撵大象3272 小时前
从三次握手到 epoll:C++ 写出可落地的 TCP Socket 服务端
网络·c++·tcp/ip·高并发·tcp·epoll
卡卡~~2 小时前
C++学习
开发语言·c++
杜 硕2 小时前
从零开始的C++生活——第四讲
开发语言·c++·生活
努力实干3 小时前
Qt/C++ 模块化设计与工程组织
开发语言·c++·qt
Rabitebla3 小时前
【Linux底层原理】 工具篇(二):Vim 编辑器基础操作与简单配置
linux·c++·编辑器·vim
Lhan.zzZ3 小时前
Qt:动态库模块框架设计
开发语言·c++·qt
wuminyu3 小时前
JVM规范学习总结-JVM规范基本概念
java·linux·c语言·jvm·c++
Demon--hx4 小时前
jsoncpp 核心概念梳理:命名空间、Value 容器与格式化输出
c++