C++引用、移动语义与完美转发来看这篇就够了

左值、右值

        左值:有变量名、可以出现在等号左边或右边,可以被赋值、可以被取地址,例如:

int a;
a = 5;
int* p = &a;
*p = 10;

        右值:没有变量名、不能出现在等号左边,只能出现在等号右边,不能被取地址,例如:

int a = 5;
int b = a;
//5 = b;错误,5是一个字面常量

int c = a + b;
//(a+b) = 20;错误,a+b属于一个临时变量,属于右值

int func(int a,int b)
{
    return a + b;
}
//func(1,2) = 10;错误,func(int a ,int b)的返回值是一个临时变量,属于右值

左值引用、右值引用

        左值引用:只能对左值进行引用的类型,但是常量左值引用可以对右值进行引用,保证了不会对引用进行修改,例如:

int a = 5;
int& b = a;//correct! b是一个左值引用,引用左值a,即b就是a的别名
int& c = 10;//error! 非常量引用c不能引用一个右值
const int& d = 20;//correct! d是一个常量引用,可以引用一个右值

        右值引用:只能对右值进行引用,区别于左值引用,可以利用此特性提供重载,用于移动构造中,例如:

int a&& = 5;//correct!a是一个右值引用

class object{
    public:
        object(const object& obj)//拷贝构造函数
        {
            m_data = new int[obj.m_size];
            memcpy(m_data,obj.m_data,obj.m_size);
            m_size = obj.m_size;
        }
        object(object&& obj)//移动构造函数
        {
            m_data = obj.m_data;
            obj.m_data = nullptr;
            m_size = obj.m_size;
        }

    private:
        int* m_data;
        int m_size
};

        引用在c++中是一个特别的类型,因为它的值类型和变量类型不一样,引用这个变量是一个引用类型,但是它的值类型不是, 左值引用和右值引用变量的值类型都是左值, 而不是左值引用或者右值引用。

int a = 5;
int &b = a;
int&& c = 10;

b = 15;//correct!,b的值类型是int,不是int&
c = 20;//correct!,c的值类型是int,不是int&&

万能引用

        前面说过右值引用只可以引用右值,但是在模板中,T&&并不表示这是一个右值引用,而是一个万能引用,英文叫做Universal References,而且可以接收左值或者右值,例如:

template<typename T>
class object{
    object(T&& t);
};

int main()
{
    int a = 5;
    object<int> obj(a);//correct!
    object<int> obj1(5);//correct!
    return 0;
}

        但是不是所有的模板引用都是万能引用,万能引用仅发生在类型推导时,即已经明确的类型,加上&&就是右值引用,例如:

template<typename T>
class object{
    object(T&& obj);//属于万能引用,因为类型需要进行推导
};

template<typename T>
class object{
    object(object && obj);//是右值引用,因为类型是明确的
};

万能引用的折叠规则:

        T&、T& &&和T&& &都会被推导为左值引用T&;

        T&& &&才会被推导为T&&;

std::move

        std::move的作用是将一个左值/右值强制转化为右值引用,继而可以通过右值引用使用该值,所以称为移动语义。std::move会将对象的状态或者所有权从一个对象转移到另一个对象,不会有内存的迁移或者内存拷贝,性能会得到改善和提升。 

        需要注意:传给std::move的参数,在调用之后,就不能再使用了。

函数原型:

template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
	return static_cast<typename remove_reference<T>::type&&>(t);
}

/// remove_reference
template<typename _Tp>
struct remove_reference
{ typedef _Tp   type; };

template<typename _Tp>
struct remove_reference<_Tp&>
{ typedef _Tp   type; };

template<typename _Tp>
struct remove_reference<_Tp&&>
{ typedef _Tp   type; };

        可以看出,std::move原型的实现最终就是将传入的所有类型转化为一个右值引用返回。

        举个例子,下面分别在拷贝和移动到其他对象之后,原对象的值输出对比:

#include<iostream>
#include<string>
#include <utility>
#include<vector>

int main()
{
    std::string str = "hello word";
    std::string strLeftRef(str);
    std::cout << "str:\"" << str << "\" copy str:" << strLeftRef << std::endl;
    std::string strRightRef(std::move(str));
    std::cout << "str:\"" << str << "\" move str:" << strRightRef << std::endl;
   
    return 0;
}
输出:
str:"hello word" copy str:hello word
str:"" move str:hello word

std::forward

        万能引用只是提供了能够同时接收左值引用和右值引用的能力,但是引用类型的唯一作用就是限制了接收的类型,后续再去使用这个引用类型的变量时就退化成了左值,例如:

void func(int&& a)
{

}

template<typename T>
class object{
public:
    void Func(T&& a)
    {
        func(a);
    }
};

int main()
{
    object<int> obj;
    obj.Func(1);
    return 0;
}

编译时会报错:
 error: cannot bind rvalue reference of type 'int&&' to lvalue of type 'int'
         func(a);

        报错原因是:不能绑定右值引用类型"int&&" 到左值类型"int"上,根本原因就是在万能引用传递后,a退化为了int类型。

        如果希望能够在传递过程中保持它的左值或者右值的属性, 那么就要使用forward来解决这个问题,下图将forward的原型引入

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值