c++11右值引用、移动语义和完美转发


前言

希望通过本篇文章能让你了c++右值引用的目的,以及其和移动语义、完美转发的联系及用法!


一、右值引用

1.左值和右值

到底什么时候是左值?什么时候是右值?是不是有点混乱?
在 C++ 中,每个表达式(expression)都有两个特性:

  1. has identity? —— 是否有唯一标识,比如地址、指针。有唯一标识的表达式在 C++ 中被称为glvalue(generalized lvalue)(广义左值)。
  2. can be moved from? —— 是否可以安全地移动(编译器)。可以安全地移动的表达式在 C++ 中被成为 rvalue。

根据这两个特性,可以将表达式分成 4 类:

  • has identity and cannot be moved from - 这类表达式在 C++ 中被称为 lvalue。
  • has identity and can be moved from - 这类表达式在 C++ 中被成为 xvalue(expiring value)(到期值)。
  • does not have identity and can be moved from - 这类表达式在 C++ 中被成为 prvalue(pure rvalue)(纯右值)。
  • does not have identity and cannot be moved -C++ 中不存在这类表达式。

简单总结一下这些 value categories 之间的关系:

  • 可以移动的值都叫 rvalue,包括 xvalue 和 prvalue。
  • 有唯一标识的值都叫 glvalue,包括 lvalue 和 xvalue。
  • std::move 的作用就是将一个 lvalue 转换成 xvalue。

2.右值的语法

1.对常量(右值)右值引用

int num = 10;
//int && a = num;  //右值引用不能初始化为左值
int && a = 10;     //右值引用能初始化为常量
a=100;             //右值引用能对右值进行修改
const int && a=100;//c++允许常量右值引用 编译器不会报错

2.对于可移动的左值的右值引用 在此之前先介绍std::move函数,move 本意为 “移动”,但该函数并不能移动任何数据,它的功能很简单,就是将某个左值强制转化为右值。

这里就要注意:move实际上是一种资源的转移,可以理解为一个对象对另一个对象资源的窃取,被窃取的对象在后续不能被继续拿来使用。

int a=10;
int &&b=std::move(a);//a本身是一个左值 move	强制将左值转化为右值

3.右值引用的提出是是为了实现后面的移动语义和完美转发。

3.将亡值

在C++11之前的右值和C++11中的纯右值是等价的。C++11中的将亡值是随着右值引用的引入而新引入的。换言之,“将亡值”概念的产生,是由右值引用的产生而引起的,将亡值与右值引用息息相关。所谓的将亡值表达式,如下列表达式:
1)返回右值引用的函数的调用表达式 2)转换为右值引用的转换函数的调用表达

二、移动构造和移动赋值

1.对于获取另一个对象的资源,我们在c++11之前采用的都是对别人资源进行拷贝,自己需要有自己的内存来存储复制过来的资源。

2.c++11之后有了移动语义,对于获取另一个对象的资源更像是对象的资源转让,用更加形象的词来描述是一个对象对另一个对象资源的“窃取”,像是形成了一种规定:一个对象把另一个对象的资源占为己有,另一个对象失去原来的资源也不会去使用它。

3.移动语义的具体实现是通过类中的移动构造和移动复制来实现的。

当类中有主动申请堆区内存的指针为成员变量的时候,移动语义的便捷性就体现的淋漓尽致!!!

#include 
class Person
{
private:
    int *m_age;
public:
    //无参构造
    Person(){}

    //拷贝构造 传左值常量引用
    Person(const Person & p): m_age(new int(*(p.m_age))){}

    //移动构造 传右值引用
    Person(Person&& p):m_age(p.m_age)
    {p.m_age= nullptr;}

    //移动赋值运算符 传右值引用
    Person& operator=(Person&&p){
        if(this==&p||p.m_age== nullptr) return *this;
        else {this->m_age=p.m_age;p.m_age= nullptr;return *this;}
    }

    //析构函数
    ~Person(){delete m_age;}
};
int main()
{
    Person s;
    //move函数强制将左值转换为右值
    Person s2(std::move(s));//调用移动构造函数
    Person s3=std::move(s2);//调用移动赋值运算符重载函数
}   

三、完美转发

1.首先解释一下什么是完美转发,它指的是函数模板可以将自己的参数“完美”地转发给内部调用的其它函数。所谓完美,即不仅能准确地转发参数的值,还能保证被转发参数的左、右值属性不变。

2.c++11之后的完美转发实际上是利用了右值引用的万能性,既能够传左值又能传右值,所以右值引用又称万能引用。

3.无论函数模板传的值是左值还是右值,我们都需要解决另一个问题,如何将函数模板接收的形参,连同其属性一起传递给被调用的函数,要使用到std::forward()模板函数,它可以很好地解决这个问题。

#include 

//测试结果
void show(const int &a)
{
    std::cout<<"rvalue"<<std::endl;
}
void show(int &a)
{
    std::cout<<"lvalue"<<std::endl;
}

//完美转发函数模板
template<typename T>
void func(T&&t)
{
    show(std::forward<T>(t));//左值、右值及其属性。
}
int main()
{
    func(10);//右值
    int t=10;
    func(t);   //左值
}

测试结果:

rvalue lvalue

总结

总而言之,右值引用是一个万能引用,它的出现是为了实现移动定义和c++11之后的完美转发!
另外,这是来自一个小白c++学习者的第一篇博客,尝试着分享自己的所得,希望能和大家一起进步,有啥error望大家指出。

右值定义参考:添加链接描述

你可能感兴趣的:(c++,开发语言)