前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【C++11】万能引用与完美转发

【C++11】万能引用与完美转发

作者头像
YIN_尹
发布2024-01-23 14:47:14
1040
发布2024-01-23 14:47:14
举报
文章被收录于专栏:YIN_尹的博客YIN_尹的博客

1. 模板中的&&—万能引用

首先我们来看这样一段代码:

在这里插入图片描述
在这里插入图片描述

这里有4个函数,我们很容易能看出来它们是一个重载的关系 然后我们给这样一个函数模板

在这里插入图片描述
在这里插入图片描述

大家看这个函数模板的参数,T&& t

这里有两个&&,所以它是右值引用吗?

不是的! 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。 我们实例化这个函数模板的时候

在这里插入图片描述
在这里插入图片描述

可以传左值,也可以传右值。 我们传的是左值,那参数t就是左值引用,我们传的是右值,参数t就是右值引用。 所以有些地方也把它叫做引用折叠,就是我们传左值的时候,它好像就把&&折叠了一下一样。 但是,大家看到我们这里接收t之后又往下传了一层

在这里插入图片描述
在这里插入图片描述

那大家就要思考一下在PerfectForward函数内部t又往下传给了Fun,那传给Fun的话t会匹配什么呢?

那我们来运行一下程序看看结果:

在这里插入图片描述
在这里插入图片描述

欸!怎么回事啊? 为什么全部匹配的都是左值引用啊!

那这里为什么会这样呢?

还记不记得上一篇文章里面又给大家提过一个东西:

就是右值不能取地址,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址。 例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地 址,也可以修改rr1。

在这里插入图片描述
在这里插入图片描述

那可以取地址、可以赋值的话他不就变成左值了嘛。 所以,一个右值被右值引用后属性会变成左值 那想一想其实这样设计也是合理的: 比如这个场景

在这里插入图片描述
在这里插入图片描述

转移资源也可以认为是修改它了,而临时变量或匿名对象这样将亡值是不能修改的。

那再回过头来看我们上面的问题,大家就应该明白了

在这里插入图片描述
在这里插入图片描述

即使我们传过去的是右值,那它被右值引用之后也会变成左值,所以里面再传给Fun都匹配的是左值。

所以说:

模板的万能引用只是提供了能够同时接收左值和右值的能力,作用就是限制了接收的类型,但在后续使用中都退化成了左值。 但是有些场景下我们希望能够在传递过程中保持它的左值或者右值的属性,那要如何做到呢? 就需要用我们下面学习的完美转发

2. 完美转发及其应用场景

首先我们来看一个对应的场景:

我们之前模拟实现过list,搞一份过来

在这里插入图片描述
在这里插入图片描述

有些用不到的东西就给它删了。 配合我们自己搞的那个string,把string里面我们添进去的移动拷贝和移动构造我也先注释掉 我写这样一段代码

在这里插入图片描述
在这里插入图片描述

那我们之前的实现并没有移动语义,所以

在这里插入图片描述
在这里插入图片描述

全是深拷贝,不论左值还是右值。 那我们把string的移动构造和移动拷贝放出来,然后我给list的push_back增加右值引用的版本

在这里插入图片描述
在这里插入图片描述

但是我们的push_back复用了insert,所以insert也增加右值引用版本

在这里插入图片描述
在这里插入图片描述

再来运行

在这里插入图片描述
在这里插入图片描述

欸,怎么还不行,list的push_back还是都是深拷贝啊

问题出在哪里呢?

🆗,是不是就是我们上面提到的问题啊! 右值被右值引用后就变成了左值。 在第一次传递给push_back 的参数,右值的话就调用右值引用版本的push_back ,但是push_back里面调用insert第二次传递,就变成左值了

在这里插入图片描述
在这里插入图片描述

所以最终不论是右值还是左值的push_back,最终调的insert都是左值版本的,所以都是深拷贝

那如何解决这个问题呢?

如何在在传递过程中保持它的左值或者右值的属性呢? 这就要用到完美转发 std::forward 完美转发在传参的过程中保留对象原生类型属性

在这里插入图片描述
在这里插入图片描述

也是库里面提供的一个函数模板 那我们直接调用forward来保持参数的原生属性

在这里插入图片描述
在这里插入图片描述

那我们再来运行

在这里插入图片描述
在这里插入图片描述

哎呀,怎么还不行? 🆗,因为下面其实还有一层传递

在这里插入图片描述
在这里插入图片描述

insert里面创建新结点的时候要调用node的构造函数

在这里插入图片描述
在这里插入图片描述

所以这里也要加forward 但是!

在这里插入图片描述
在这里插入图片描述

node的构造函数我们只有左值引用的版本 所以,我们要再增加一个右值引用的版本

在这里插入图片描述
在这里插入图片描述

并且,这里_data的初始化我们也要用forward保持x它的属性,因为我们现在存string,他会调string的构造,这里保持它是右值,才会调到右值引用版本的移动拷贝 那这下

在这里插入图片描述
在这里插入图片描述

就可以了,右值的push_back就是移动拷贝了

那有了完美转发我们最开始那个场景:

在这里插入图片描述
在这里插入图片描述

都匹配的是右值引用的版本 怎么办? 加个完美转发就可以了

在这里插入图片描述
在这里插入图片描述

3. 用到的代码

3.1 string.h

代码语言:javascript
复制
#pragma once

namespace bit
{
	class string
	{
	public:
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}

		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			//cout << "string(char* str)" << endl;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		// s1.swap(s2)
		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		// 拷贝构造
		string(const string& s)
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;
			string tmp(s._str);
			swap(tmp);
		}
		// 移动构造
		string(string&& s)
			:_str(nullptr)
		{
			cout << "string(string&& s) -- 移动拷贝" << endl;
			swap(s);
		}
		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 深拷贝" << endl;
			string tmp(s);
			swap(tmp);
			return *this;
		}
		// 移动赋值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) -- 移动赋值" << endl;
			swap(s);
			return *this;
		}
		~string()
		{
			delete[] _str;
			_str = nullptr;
		}
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}
		void push_back(char ch)
		{
			if (_size >= _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}
			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}
		//string operator+=(char ch)
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		string operator+(char ch)
		{
			string tmp(*this);
			tmp += ch;
			return tmp;
		}
		const char* c_str() const
		{
			return _str;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity; // 不包含最后做标识的\0
	};
	string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}
		string str;
		while (value > 0)
		{
			int x = value % 10;
			value /= 10;
			str += ('0' + x);
		}
		if (flag == false)
		{
			str += '-';
		}
		std::reverse(str.begin(), str.end());
		return str;
	}
}

3.2 list.h

代码语言:javascript
复制
#pragma once
#include<assert.h>

namespace yin
{
	template<class T>
	struct list_node
	{
		list_node<T>* _next;
		list_node<T>* _prev;
		T _data;

		list_node(const T& x = T())
			:_next(nullptr)
			, _prev(nullptr)
			, _data(x)
		{}

		list_node(T&& x = T())
			:_next(nullptr)
			, _prev(nullptr)
			, _data(forward<T>(x))
		{}
	};


	// 1、迭代器要么就是原生指针
	// 2、迭代器要么就是自定义类型对原生指针的封装,模拟指针的行为
	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
		typedef list_node<T> node;
		typedef __list_iterator<T, Ref, Ptr> self;
		node* _node;

		__list_iterator(node* n)
			:_node(n)
		{}

		Ref operator*()
		{
			return _node->_data;
		}

		Ptr operator->()
		{
			return &_node->_data;
		}

		self& operator++()
		{
			_node = _node->_next;

			return *this;
		}

		self operator++(int)
		{
			self tmp(*this);
			_node = _node->_next;

			return tmp;
		}

		self& operator--()
		{
			_node = _node->_prev;

			return *this;
		}

		self operator--(int)
		{
			self tmp(*this);
			_node = _node->_prev;

			return tmp;
		}

		bool operator!=(const self& s)
		{
			return _node != s._node;
		}

		bool operator==(const self& s)
		{
			return _node == s._node;
		}
	};


	template<class T>
	class list
	{
		typedef list_node<T> node;
	public:
		typedef __list_iterator<T, T&, T*> iterator;
		typedef __list_iterator<T, const T&, const T*> const_iterator;

		iterator begin()
		{
			//iterator it(_head->_next);
			//return it;
			return iterator(_head->_next);
		}

		const_iterator begin() const
		{
			return const_iterator(_head->_next);
		}

		iterator end()
		{
			return iterator(_head);
		}

		const_iterator end() const
		{
			//iterator it(_head->_next);
			//return it;
			return const_iterator(_head);
		}

		void empty_init()
		{
			_head = new node(T());
			_head->_next = _head;
			_head->_prev = _head;
		}

		list()
		{
			empty_init();
		}

		template <class Iterator>
		list(Iterator first, Iterator last)
		{
			empty_init();

			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		void swap(list<T>& tmp)
		{
			std::swap(_head, tmp._head);
		}

		list(const list<T>& lt)
		{
			empty_init();

			list<T> tmp(lt.begin(), lt.end());
			swap(tmp);
		}

		// lt1 = lt3
		list<T>& operator=(list<T> lt)
		{
			swap(lt);
			return *this;
		}

		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}

		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				//it = erase(it);
				erase(it++);
			}
		}

		void push_back(const T& x)
		{
			insert(end(), x);
		}

		void push_back(T&& x)
		{
			insert(end(), forward<T>(x));
		}

		void push_front(const T& x)
		{
			insert(begin(), x);
		}

		void pop_back()
		{
			erase(--end());
		}

		void pop_front()
		{
			erase(begin());
		}

		void insert(iterator pos, const T& x)
		{
			node* cur = pos._node;
			node* prev = cur->_prev;

			node* new_node = new node(x);

			prev->_next = new_node;
			new_node->_prev = prev;
			new_node->_next = cur;
			cur->_prev = new_node;
		}

		void insert(iterator pos, T&& x)
		{
			node* cur = pos._node;
			node* prev = cur->_prev;

			node* new_node = new node(forward<T>(x));

			prev->_next = new_node;
			new_node->_prev = prev;
			new_node->_next = cur;
			cur->_prev = new_node;
		}

		iterator erase(iterator pos)
		{
			assert(pos != end());

			node* prev = pos._node->_prev;
			node* next = pos._node->_next;

			prev->_next = next;
			next->_prev = prev;
			delete pos._node;

			return iterator(next);
		}
	private:
		node* _head;
	};
}

3.3 test.cpp

代码语言:javascript
复制
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }

template<typename T>
void PerfectForward(T&& t)
{
	Fun(forward<T>(t));
}

int main()
{
	PerfectForward(10); // 右值
	int a;
	PerfectForward(a); // 左值
	PerfectForward(move(a));// 右值

	const int b = 8;
	PerfectForward(b);// const 左值
	PerfectForward(move(b)); // const 右值

	return 0;
}

//#include "list.h"
//
//int main()
//{
//	yin::list<bit::string> lt;
//
//	bit::string s1("hello world");
//	lt.push_back(s1);
//
//	lt.push_back(bit::string("hello world"));
//	lt.push_back("hello world");
//
//	return 0;
//}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-01-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 模板中的&&—万能引用
  • 2. 完美转发及其应用场景
  • 3. 用到的代码
    • 3.1 string.h
      • 3.2 list.h
        • 3.3 test.cpp
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档