C++ | 左值、右值、移动构造函数

目录

一、左值、右值

二、移动构造函数

1.不使用移动构造函数

2.使用移动构造函数

三、通用引用

四、std::move 

五、引用折叠


一、左值、右值

        右值是C++从C继承来的概念,最初是指=号右边的值。但现在C++中的右值已经与它最初的概念完全不一样了。在C++中右值指的的临时值或常量,更准确的说法是保存在CPU寄存器中的值为右值,而保存在内存中的值为左值。

    int a = 5;

        一个常数5,我们在使用它时不会在内存中为其分配一个空间,而是直接把它放到寄存器中,所以它在C++中就是一个右值。再比如说我们定义了一个变量 a,它在内存中会分配空间,因此它在C++中就是左值。

        在C++中使用&&表示右值引用,右值引用只能接收右值。

    int && a = 5;//常数5是右值,&&a表示右值引用,注意:a是左值
    int b = 10;  //b是左值
    int && c = b;//错误 

二、移动构造函数

1.不使用移动构造函数

#pragma once
#include 
#include 
class classA {
public:
    classA() {
        std::cout << "A 构造函数" << std::endl;
        p = new int();
    }
    classA(const classA& a)
    {
        std::cout << "A 拷贝构造函数" << std::endl;
        p = new int();
        memcpy(p, a.p, 4);
    }
    ~classA() {
        std::cout << "A 析构函数" << std::endl;
        if (p)
        {
            delete p;
        }
    }
private:
    int* p;
};
class StudyRightValue
{
public:
    void TestNotUseRightValue() {
        std::vector vectorA;
        classA a1;
        vectorA.push_back(a1);
    }
};

//结果
//A 构造函数
//A 拷贝构造函数
//A 析构函数
//A 析构函数

        对象a1放入vector时,在vector内部又创建了一个对象,并调用了其拷贝构造函数进行了深拷贝。 当有大量的classA对象存入vector中时会严重影响性能。

2.使用移动构造函数

        移动构造函数起什么作用呢?就像它的名字一样,它可以实现指针的移动,即可以将一个对象中的指针成员转移给另一个对象。指针成员转移后,原对象中的指针成员一般要被设置为NULL,防止其再被使用。

        移动构造函数和其它构造函数不同,此构造函数使用右值引用形式的参数。

        以我们上面的代码为例,如果我们有了移动构造函数,那么在将classA对象push到vector时,vector内部虽然还是会再分classA对象,但在进行数据的拷贝时就不是深拷贝(memcpy)了,而变成了浅拷贝(指针地址赋值),这样就大大提高了程序的执行效率。

    classA(classA&& a)
    {
        std::cout << "A 移动构造函数" << std::endl;
        p = a.p;
        a.p = nullptr;
    }

       默认情况下,左值初始化同类对象只能通过拷贝构造函数完成,如果想调用移动构造函数,则必须使用右值进行初始化。C++11 标准中为了满足用户使用左值初始化同类对象时也通过移动构造函数完成的需求,新引入了 std::move() 函数,它可以将左值强制转换成对应的右值,由此便可以使用移动构造函数。 

class StudyRightValue
{
public:
    void TestNotUseRightValue() {
        std::vector vectorA;
        classA a1;                       //a1是左值。
        vectorA.push_back(std::move(a1));//注意这一行,右值引用只能接收右值。
    }
};
//结果
//A 构造函数
//A 移动构造函数
//A 析构函数
//A 析构函数

        在实际开发中,通常在类中自定义移动构造函数的同时,会再为其自定义一个适当的拷贝构造函数,由此当用户利用右值初始化类对象时,会优先调用移动构造函数;使用左值初始化类对象时,会调用拷贝构造函数。

        要注意移动构造函数的使用场景,一般只用于内存较大的临时变量。因为指针成员转移后,原对象中的指针成员一般要被设置为NULL,此时,如果原对象中的其他函数要使用该指针会出错。

三、通用引用

        当右值引用和模板结合的时候,T&& 并不一定表示右值引用,它可能是个左值引用又可能是个右值引用 (这里的 && 是一个未定义的引用类型,称为通用引用universal references ),它必须被初始化,它是左值引用还是右值引用却决于它的初始化:

  • 如果它被一个左值初始化,它就是一个左值引用;
  • 如果被一个右值初始化,它就是一个右值引用。

       只有当发生自动类型推断时(如函数模板的类型自动推导,或auto关键字)并且是T&&这种形式,&& 才是一个通用引用,如上面提到的std::move()函数。

template 
_NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept { // forward _Arg as movable
    return static_cast&&>(_Arg);
}

四、std::move 

        std::move( )函数的返回值是remove_reference<_Ty>::type,其定义如下:

template 
using remove_reference_t = typename remove_reference<_Ty>::type;
template 
struct remove_reference {
    using type                 = _Ty;
    using _Const_thru_ref_type = const _Ty;
};

template 
struct remove_reference<_Ty&> {
    using type                 = _Ty;
    using _Const_thru_ref_type = const _Ty&;
};

template 
struct remove_reference<_Ty&&> {
    using type                 = _Ty;
    using _Const_thru_ref_type = const _Ty&&;

         由代码可知,std::move()返回的是remove_reference类的type类型成员,而remove_reference的作用就是去掉引用。所以当传入的类型是int、int&、int&&时,返回的type都是int,再经过强制转换static_cast转为右值。

五、引用折叠

        千言万语汇成一句话:所有的右值引用叠加到右值引用上仍然使一个右值引用。所有的其他引用类型之间的叠加都将变成左值引用。

你可能感兴趣的:(C++,c++,右值,移动构造函数)