左值、右值、std::move

1.浅拷贝和深拷贝

左值和右值使用得当可以减少开销和提升性能,在代码的层面来说,提升性能的点在于浅拷贝和深拷贝。
定义一个带指针的类,编写一个浅拷贝和深拷贝的成员函数:

class PClass {

public:
    PClass(int num) : m_num(num) { 
        m_p = new int[num]; 
        for (int i = 0; i < num; ++i) {
            m_p[i] = i;
        }
    }
    ~PClass() {
        if (m_p) {
            delete m_p;
        }
    }
    // 浅拷贝
    void shallowCopy(PClass& p) {
        m_num = p.m_num;
        m_p = p.m_p;
        p.m_p = nullptr;
    }
    // 深拷贝
    void deepCopy(PClass& p) {
        m_num = p.m_num;
        m_p = new int[m_num];
        for (int i = 0; i < m_num; ++i) {
            m_p[i] = p.m_p[i];
        }
    }

private:
    int* m_p = nullptr;
    int m_num;
};

int main()
{
    PClass a = PClass(10);
    PClass b;
    PClass c;
    b.deepCopy(a);
    c.shallowCopy(a); // 转移a中m_p指向的内存的使用权给c
    return 0;
}

可以看到深拷贝会重新申请内存空间,b把拷贝对象a中申请的内存的数据逐个拷贝至新对象b中,这种拷贝方法开销较大。
而浅拷贝仅仅只是把a的m_p指针指向的内存的使用权”转移“给新的对象c,并未申请新的内存空间实际拷贝,这种方法开销较少,但要保证a无法再操作该内存了,否则会出现问题。

如果a是一个临时对象,被拷贝后就不存在了,那么就能保证浅拷贝不出问题。

2.左值和右值

c98中的左值和右值
左值:持久存在的值,一般有变量名。
右值:表达式结束后就会消失的值,一般没有变量名。字面值(不包括字符串字面值)、临时的表达式值、临时的函数返还值这些短暂存在的值都是右值。

字符串字面值存储在静态内存区,是持久存在的,因此不属于右值

    int a; // 左值
    int b = a + 1; // a + 1表达式的返回值为右值
    int c = 1; // 1为右值
    fun(); // 右值

3.右值引用与std::move

c++中&表示左值引用,如果作为形参,则匹配左值:

void fun(PClass& p); // 该函数接受一个左值作为参数

在c++11中引入了右值引用,用&&表示

void fun(PClass&& p); // 该函数接受一个右值作为参数

c++11提供std::move,可以将左值转换为右值引用,虽然这个值可以一直存在,并不是个临时的、马上就会消失的值。

此时,就可以更优雅的解决浅拷贝和深拷贝的问题了,为PClass增加拷贝构造函数和移动构造函数:

class PClass {

public:

   ...
   
    // 拷贝构造函数接受一个右值 进行浅拷贝 即移动构造函数
    PClass(PClass&& p) {
        std::cout << "Move construct" << std::endl;
        m_num = p.m_num;
        m_p = p.m_p;
        p.m_p = nullptr;
    }
    // 拷贝构造函数接受一个左值 进行深拷贝 即拷贝构造函数
    PClass(PClass& p) {
        std::cout << "Copy construct" << std::endl;
        m_num = p.m_num;
        m_p = new int[m_num];
        for (int i = 0; i < m_num; ++i) {
            m_p[i] = p.m_p[i];
        }
    }
    
    ...
};

int main()
{
    PClass a(10);

    PClass b(a); // 调用拷贝构造函数
    PClass c(std::move(a)); // 将b转换为右值引用 此时调用PClass的移动构造函数
    // a中的资源使用权交给了c 采用浅拷贝减少内存开销 但要保证a不会再操作这部分内存或者a不会再被使用

    return 0;
}

此时如果用右值作为参数去构造PClass,就会调用移动构造函数,从使用浅拷贝减少开销。
但要注意的是:右值引用一般只用在函数返回值和形参中,如果定义一个右值,并不能作为一个右值来使用

int main()
{
    PClass&& e = PClass(); // 定义一个右值引用
    PClass f(e); // 使用该右值引用实例化PClass 此时发现调用的是拷贝构造函数 也就是说e被当做左值来使用
    
    return 0;
}

右值引用类型只是用于匹配右值,而并非表示一个右值。因此,尽量不要声明右值引用类型的变量。

3.左值、将亡值、纯右值

c++11中进行了如下分类,目的是为了更好的符合移动语义

  • 左值:指持久存在的对象,或者左值引用的返回值
  • 将亡值:右值引用的返回值
  • 纯右值:即之前提到的右值(临时对象)

将亡值和纯右值都是右值

PClass& func1();
PClass&& func2();
PClass func3();

int main(){
    PClass a;
    
    a;              //左值表达式
    func1();        //左值表达式,因为返回值类型是左值引用
    func2();        //将亡值表达式,因为返回值类型是右值引用,即使指代的对象即使非临时。
    func3();        //纯右值表达式,返回值为临时值。
    std::move(a)//将亡值表达式,std::move本质也是个函数,同上。
    PClass();       //纯右值表达式
}

std::move函数的实现原理就是的将参数强转成右值引用类型返回

4.参数和返回值

比较常用的地方就是函数在参数传递时,引起的拷贝构造和移动构造,此处可能决定开销大小。

void fun1(PClass p) { return; }
void fun2(PClass&& p) { return; }

int main()
{
    PClass a(10);
    PClass& b = a;
    PClass c(10);
    PClass d(10);

    fun1(a); // a为左值 引起拷贝构造
    fun1(b); // b为左值 引起拷贝构造

    fun1(std::move(c)); // fun1的实参为右值 实例化fun1参数p时引起移动构造 c中的数据交给参数p c中m_p为nullptr (1)
    fun1(d); // fun1的实参为左值 引起拷贝构造 (2)

    fun2(std::move(d)); // 未引起任何构造函数调用 此处虽然fun2的实参为右值 但并不是临时值 而是持久存在的 因此右值不能再理解为临时值了

    return 0;
}

可以看到代码中(1) (2)处如果c和d不再使用,则使用std::move转换为右值,(1)相比(2)处,会减少开销。

对于返回值:

PClass fun1() { PClass a(10); return a; }

int main()
{
    PClass a = fun1(); // 因为fun1()的返回值为右值 所以此处引起移动构造函数的调用 减少开销
    PClass b(fun1()); // 因为fun1()的返回值为右值 所以此处引起移动构造函数的调用 减少开销
    return 0;
}

参考:
https://www.cnblogs.com/KillerAery/p/12802771.html

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值