左值引用

左值引用就是左值的别名,不是变量的别名,而是地址的别名,是与地址建立的一种映射关系。我们可以通过不同的别名访问同一块地址空间。

左值引用并非对象,只是为一个已经存在的对象所起的另外一个名字。

左值引用必须初始化,因为无法令左值引用重新绑定到另外一个对象。一旦初始化完成,左值引用将和他的初始值对象一直绑定在一起。

#include <iostream>
int main()
{
    int x = 1;
    int &y = x;

    // output: 0x7ffc224801bc 0x7ffc224801bc
    std::cout << &x << " " << &y << std::endl;
}

右值引用

右值引用是 C++11 为了实现移动语义(move semantic)和完美转发(perfect forwarding)而设计出来的新的数据类型,它的主要目的有两个方面:

  • 消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。
  • 能够更简洁明确地定义泛型函数。

右值引用,简单说就是绑定在右值上的引用,右值引用必须立即进行初始化操作,且只能使用右值进行初始化。

int && a = 10;

和常量左值引用不同的是,右值引用还可以对右值进行”修改”:

int && a = 10;
a = 100;
// output: 100
cout << a << endl;

字面常量10被存储在代码区,代码区对我们来说是只读的,我们不能修改其中的数据,所以编译器先在可写数据区创建只读数据10的一份拷贝,再把位于可写数据区的拷贝的地址给引用a,让引用封装这个地址。所以可以通过右值引用修改的数据,只是只读数据的一份拷贝,并不是真正的只读数据。

实现移动语义

移动语义拷贝语义相对的,移动语义可以将资源 ( 堆,系统对象等 ) 从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁。

要实现移动语义,需要定义移动构造函数,还可以定义移动赋值操作符。对于右值的拷贝和赋值会调用移动构造函数和移动赋值操作符。

 在转移资源后,被移动的对象处于“有效但未定义的状态”(valid but unspecified state)

struct Foo {
   Foo() { std::cout << "Constructed" << std::endl; }
   Foo(const Foo &) { std::cout << "Copy-constructed" << std::endl; }
   Foo(Foo &&) { std::cout << "Move-constructed" << std::endl; }
   ~Foo() { std::cout << "Destructed" << std::endl; }
};

int main()
{
    Foo f1;                   // 默认构造函数
    Foo f2 = f1;              // 拷贝构造函数
    Foo f3 = std::move(f1);   // 移动构造函数
    // Foo&& f4 = std::move(f1); // 不调用构造函数?
}

output:

Constructed
Copy-constructed
Move-constructed
Destructed
Destructed
Destructed

实现完美转发

引用绑定规则