[读书笔记][深入应用C++11]2.1 右值引用

C++98中的左值与右值

在C++98/03中,只有左值与右值两种类型值类型。左值是指表达式结束后依然存在的持久对象,右值是指表达式结束时就不存在的临时对象。区分左值和右值得方法是:能不能对表达式取地址,如果能则为左值,否则为右值。

C与C++98中右值的区别

C++检测到右值在内存中存在对应的实体,会自动转换为左值。
例如:以下代码在C中无法编译通过,因为其对右值进行操作;而在C++中可以编译通过,因为其自动换为左值a,对a进行操作。

int a=0;
(a=1)++;

以下代码在C++中也编译不过,因为a+1是右值,且在内存中无对应的实体。

int a=0;
(a+1)++;

C++11中的左值、将亡值与纯右值

在C++11中,右值由两个概念构成:将亡值与纯右值。C++11中的所有值必属于左值、将亡值、纯右值之一。

左值引用与右值引用

左值引用是:对左值的引用。如:

int number=10;
int& refnum=number;

右值引用:对右值(也可以是左值)的引用。如:

int&& refnum=10;
Object&& refnum=getObject();

通过右值引用的声明,延长了右值得生命周期,使得其生命周期与右值引用变量的生命周期一样。通过右值引用,对象从函数返回值返回时,不需要调用拷贝构造函数创建新对象,提高了效率。

&&的特性

未定引用类型

未定引用类型是指:未确定引用是左值引用还是右值引用,这取决于初始化的值。如果其被左值初始化,则它是左值引用;如果其被右值初始化,则它是右值引用。
只有发生自动类型推断时(如函数模板的类型自动推导,或auto关键字),&&才是未定引用类型。

void func(Object&& param);//param是右值引用

template<typename T>
void func(T&& param);//param是左值引用

auto&& Number=10;//Number是右值引用

int Num=10;
auto&& Number=Num;//Number是左值引用

&&的总结

  1. 左值和右值是独立于它们的类型的,右值引用类型可能是左值也可能是右值。
  2. auto&&或者函数参数类型(函数模板)自动推导的T&&是一个未定的引用类型,被称为universal references,它可能是左值引用也可能是右值引用类型,取决于初始化的值类型。
  3. 所有的右值引用叠加到右值引用上仍然是一个右值引用,其他引用折叠都为左值引用。当T&&为模板参数时,输入左值,它变为左值引用,而输入右值是则变为具名的右值引用。
  4. 编译器会将具名的右值引用视为左值,而将未命名的右值引用视为右值。

右值引用优化性能,避免深拷贝(作用)

移动构造函数

移动构造函数的参数是一个右值引用类型,它只需要浅拷贝,不需要深拷贝,从而提高了性能。右值引用的一个重要的目的就是用来支持移动语义。

    MyString(MyString&& String) {
        m_len = String.m_len;
        m_data = String.m_data;
        String.m_data = NULL;
        String.m_len = 0;
        cout << "移动构造函数!" << endl;
    }

移动语义

移动语义可以将资源通过浅拷贝的方式从一个对象转移到另外一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,可以大幅度提高C++应用程序的性能,消除临时对象的维护对性能的影响。下面是移动语义的例子:

#include <vector>
#include <iostream>
using namespace std;

class MyString {
private:
    char* m_data;
    size_t m_len;
    void copy_data(const char *str) {
        m_data = new char[m_len + 1];
        memcpy(m_data, str, m_len);
        m_data[m_len] = '\0';
    }
public:
    MyString() {
        m_data = NULL;
        m_len = 0;
        cout << "构造函数!" << endl;
    }
    MyString(const char* str) {
        m_len = strlen(str);
        copy_data(str);
        cout << "构造函数!" << endl;
    }
    MyString(const MyString& String) {
        m_len = String.m_len;
        copy_data(String.m_data);
        cout << "拷贝构造函数!" << endl;
    }
    MyString(MyString&& String) {
        m_len = String.m_len;
        m_data = String.m_data;
        String.m_data = NULL;
        String.m_len = 0;
        cout << "移动构造函数!" << endl;
    }
    MyString& operator=(const MyString& String) {
        if (this != &String) {
            m_len = String.m_len;
            copy_data(String.m_data);
        }
        cout << "赋值运算符!" << endl;
        return *this;
    }

    MyString& operator=(MyString&& String) {
        if (this != &String) {
            m_len = String.m_len;
            m_data = String.m_data;
            String.m_data = NULL;
            String.m_len = 0;
        }
        cout << "移动赋值运算符!" << endl;
        return *this;
    }

    virtual ~MyString() {
        if (m_data != NULL) {
            free(m_data);
            m_data = NULL;
            m_len = 0;
        }
        cout << "析构函数!" << endl;
    }
};
int main(){
    vector<MyString> Vector;
    Vector.push_back(MyString("Hello"));
    return 0;
}

需要注意的是:在提供移动构造函数的同时也要提供拷贝构造函数,以防止移动不成功的时候还能使用拷贝构造函数。

猜你喜欢

转载自blog.csdn.net/zimengyu2020/article/details/79992543
今日推荐