c++11新特性2(右值引用)

目录

1. 右值引用的概念

2. 右值引用的使用场景

2.1 移动语义

2.2 万能引用+完美转发

2.3 emplace_back函数

3. 可变参数模板


1. 右值引用的概念

C++98中提出了引用的概念,引用即别名,引用变量与其引用实体公共同一块内存空间,而引用的底层是通过指针来实现的,因此使用引用,可以提高程序的可读性。

为了提高程序运行效率,C++11中引入了右值引用,右值引用也是别名,但其只能对右值引用。

左值和右值

  • 左值一般可以修改,可以取地址(一般就是我们定义的变量,int a = 0;)
  • 右值一般不可被修改,不能取地址(一般是临时变量)

C++11对右值进行了严格的区分:

  • C语言中的纯右值:比如,a+b, 100
  • 将亡值:比如,表达式的中间结果、函数按照值的方式进行返回。

左值引用和右值引用

  • 左值引用&  -》 可以引用左值(左值引用不可以引用右值)
  • 右值引用&& -》 可以引用右值(右值引用不能引用左值)
  • const 左值引用  -》左值 或 右值  //const左值引用可以引用右值
  • 右值引用  -》 std::move(左值)    //右值引用可以引用move之后的左值 
    // 普通类型引用只能引用左值,不能引用右值
    int a = 10;
    int& ra1 = a;
    //int& ra2 = 10;   // 编译失败,因为10是右值
    const int& ra3 = 10;//const左值引用可以引用右值
    const int& ra4 = a;

    // 10纯右值,本来只是一个符号,没有具体的空间,
    // 右值引用变量r1在定义过程中,编译器产生了一个临时变量,r1实际引用的是临时变量
    int&& r1 = 10;
    int a = 10;
    //int&& r2 = a;  // 编译失败:右值引用不能引用左值
    int && r3 = std::move(a);//右值引用可以引用move之后的左值 

2. 右值引用的使用场景

2.1 移动语义

左值引用的使用场景 1、做参数 2、做返回值。

  • 左值引用传参时减少了拷贝,提高了效率。
  • 函数返回分两种:传引用、传值,但不是所有情况都能用传引用(比如函数返回对象出了函数作用域就不在了,叫做临时对象),只能传值,传值返回一般会发生两次深拷贝(编译器会优化为一次),这样效率低下。此时,如果类里面设计了移动构造,传值返回时就会自动调用移动构造(实际上是一种资源转移的方式),提高了效率。

右值引用并不是直接使用去减少拷贝、提高效率。而是在支持深拷贝的类里面,如string、vector、list,提供移动拷贝和移动赋值。这时候类对象进行传值返回时,会调用移动拷贝和移动赋值,来转移资源,避免了深拷贝,提高了效率。

(1)移动构造,主要是解决函数传值返回时候的深拷贝问题。

        在C++11中,编译器会为类默认生成一个移动构造,该移动构造为浅拷贝,因此当类中涉及到资源管理时,用户必须显式定义自己的移动构造

		string(string&& s)//右值引用,函数返回值为临时对象时会自动匹配这个
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			//cout << "string(string&& s) -- 移动构造" << endl;
			this->swap(s);
		}

(2)移动赋值,功能和以上类似。

		//移动赋值类似上面的移动构造
		string& operator=(string&& s)
		{
			//cout << "string& operator=(string&& s) -- 移动赋值" << endl;
			this->swap(s);
			return *this;
		}

2.2 万能引用+完美转发

完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。
函数模板在向其他函数传递自身形参时,如果相应实参是左值,它就应该被转发为左值;如果相应实参是右值,它就应该被转发为右值。
这样做是为了保留在其他函数针对转发而来的参数的左右值属性进行不同处理(比如参数为左值时实施拷贝语义;参数为右值时实施移动语义)

模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,但是万能引用类型的唯一作用就是扩大了接收的类型,后续使用中都退化成了左值。
若希望能够在传递过程中保持它的左值或者右值的属性, 就需要用到完美转发forward

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(std::forward<T>(t));//在传递过程中保持左值或右值的属性
}

2.3 emplace_back函数

template< class... Args >
void emplace_back( Args&&... args ); //右值引用

在c++11的容器的插入接口函数中,如果实参是右值,则可以转移资源,减少拷贝

  • push_back:先构造一个对象 + 再拷贝构造到尾插的位置上
  • emplace_back的用法:直接传对象的参数构造到尾插的位置上

emplace_back除了用法上,和push_back没什么太大的区别。emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象

std::list< std::pair<int, char> > mylist;
mylist.push_back(pair<int, char>(40, 'd'));
mylist.push_back(make_pair(40, 'd'));
mylist.push_back({ 50, 'e' });

mylist.emplace_back(make_pair(30, 'c'));
mylist.emplace_back(10, 'a');//emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象

综上,右值引用的作用

  1. 给中间临时变量取别名:
  2. 实现移动语义(移动构造与移动赋值)
  3. 实现完美转发
  4. 实现emplace_back函数

3. 可变参数模板

有些时候,我们定义一个函数,可能这个函数需要支持可变长参数,也就是说调用者可以传入任意个数的参数。比如C函数printf()

printf("name: %s, number: %d", "Obama", 1); 

在C++11里面专门提供了对可变长参数的更现代化的支持,那就是可变长模板。

模板参数包(template parameter pack)

template <class ...Args>
void ShowList(Args... args)//Args就是一个模板参数包
{
	cout << sizeof...(args) << endl;
	//for (int i = 0; i < sizeof...(args); ++i)
	//	cout<<args[i]<<" "; // 不支持
}
int main()
{
	ShowList();
	ShowList(1);
	ShowList(1, 'A');
}

那么,如何获取每一个参数呢?

template <class T>
void ShowList(const T& t)
{
	cout << t << endl;
}

template <class T, class ...Args>
void ShowList(T value, Args... args)
{
	cout << value << " ";
	ShowList(args...);
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
右值引用和move语义是C++ 11中重要的特性之一,可以提高程序的效率和性能。右值引用是一种新的引用类型,其绑定到临时对象或将要销毁的对象上,而不是左值对象。move语义则是利用右值引用,将一个对象的资源所有权从一个对象转移到另一个对象,避免了不必要的内存拷贝,提高了程序的效率。 下面是一个使用右值引用和move语义的例子: ```c++ #include <iostream> #include <vector> using namespace std; vector<int> getVector() { vector<int> v = {1, 2, 3, 4}; return v; } int main() { vector<int> v1 = getVector(); // 拷贝构造函数 vector<int> v2 = move(v1); // 移动构造函数 cout << "v1 size: " << v1.size() << endl; // 输出 0 cout << "v2 size: " << v2.size() << endl; // 输出 4 return 0; } ``` 在上面的例子中,getVector函数返回一个临时对象vector<int>,该临时对象是一个右值。在主函数中,我们使用拷贝构造函数将临时对象的值拷贝到v1中,然后使用move函数将v1中的值移动到v2中。由于move函数使用了右值引用,将v1中的资源所有权转移到了v2中,避免了不必要的内存拷贝,提高了程序的效率。最后,我们输出v1和v2的大小,可以看到v1的大小为0,v2的大小为4,说明资源已经成功转移。 需要注意的是,使用move语义之后,原对象的值会被移动到新对象中,并且原对象的值会被置为默认值(例如,对于vector而言,原对象的大小为0)。如果需要保留原对象的值,则需要在移动之前先进行一次拷贝操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值