Skip to content

左值右值移动语义

1. 左值和右值的概念

左值与右值这两个概念是从 C 中传承而来的,左值指既能够出现在等号左边,也能出现在等号右边的变量;右值则是只能出现在等号右边的变量。

int a; // a 为左值
a = 3; // 3 为右值
  • 左值是可寻址的变量,有持久性;
  • 右值一般是不可寻址的常量,或在表达式求值过程中创建的无名临时对象,短暂性的。

左值和右值主要的区别之一是左值可以被修改,而右值不能。

2. 右值引用和移动语义

引入右值引用的目的就是为了减少内存拷贝,优化程序性能

std::move 就是将一个左值变量强行转换为右值引用,相当于static_cast(l_reference)

std::move 并不会真正地移动对象,真正的移动操作是在RD自己实现的移动构造函数中

#include <string.h>
#include <iostream>

class Test
{
    public:
        Test()
        {
            buffer = new char[1024];
            datasize = 1024;
        }
        Test(const Test& test)
        {
            // 调用深拷贝
            printf("l copy construct\n");
            this->buffer = new char[test.datasize];
            memcpy(this->buffer, test.buffer, test.datasize);
            this->datasize = test.datasize;
        }
        Test(Test&& test)
        {
            printf("r copy construct\n");
            // 自己实现真正的移动
            this->buffer = test.buffer;
            this->datasize = test.datasize;
            test.buffer = nullptr;
            test.datasize = 0;
        }
        ~Test()
        {
            if (nullptr != buffer)
            {
                delete[] buffer;
                buffer = nullptr;
            }
            datasize = 0;
        }
    private :
        char* buffer;
        int datasize;
};

int main(int argc, char* argv[])
{
    Test test;

    // 调用普通的拷贝构造函数
    Test test1(test);

    // std::move 只是告诉编译器,让其调用移动构造函数,真正的移动需要RD自己实现
    Test test2(std::move(test));

    return 0;
}

3、完美转发std::forward

当我们用一个变量接收右值引用时,根据C++ 标准的定义,这个变量变成了一个左值。那么他永远不会调用接下来函数的右值版本,这可能在一些情况下造成拷贝。

为了解决这个问题 C++ 11引入了完美转发,支持右值判断的推导,若原来是一个右值,那么他转出来就是一个右值,否则为一个左值。

#include <string.h>
#include <iostream>

class Test
{
    public:
        Test()
        {   
            buffer = new char[1024];
            datasize = 1024;
        }   
        Test(const Test& test)
        {   
            // 调用深拷贝
            printf("l copy construct\n");
            this->buffer = new char[test.datasize];
            memcpy(this->buffer, test.buffer, test.datasize);
            this->datasize = test.datasize;
        }   
        Test(Test&& test)
        {   
            printf("r copy construct\n");
            // 实现真正的移动
            this->buffer = test.buffer;
            this->datasize = test.datasize;
            test.buffer = nullptr;
            test.datasize = 0;
        }   
        ~Test()
        {   
            if (nullptr != buffer)
            {   
                delete[] buffer;
                buffer = nullptr;
            }   
            datasize = 0;
        }   
    private :
        char* buffer;
        int datasize;
};

int main(int argc, char* argv[])
{
    Test test;

    // 只是强转为r reference,没做任何其他事
    Test&& test1 = std::move(test);

    // 由于**test1这个右值引用本身是左值**,此处会调用拷贝构造
    Test test2(test1);

    // forward支持对test1类型推导,test1为右值引用,会转成右值,调用移动构造函数
    Test test3(std::forward<Test>(test1));
    return 0;
}