右值引用的有关学习

80 阅读6分钟

移动语义

  • 移动语义主要适用于自定义的类型,其中移动操作可以显著减少资源的拷贝成本。

  • 当需要将一个对象转移到一个新的智能指针或容器中时,可以使用移动语义来避免不必要的拷贝操作,提高性能和效率。

  • 右值引用变量不再拥有原始资源的所有权。资源的所有权已经转移到其他对象(如移动赋值目标),因此在使用右值引用之后,我们应该小心避免对其进行访问,以免出现悬空引用或无效操作。

  • 左值和右值是C++中非常重要的概念,理解它们有助于更好地理解语言的基本语义和编程模型。下面将详细解释左值和右值的用法,并提供示例来帮助你学习它们。

1 左值和右值

左值(Lvalue):

可以修改的值

  1. 变量: 变量是最常见的左值,因为它们具有名称,可以用于存储和修改值。

    int x = 10; // x 是左值
    int y = 20;
    int c = x + y; // x+y为右值,c为左值
    
  2. 数组元素: 数组元素也是左值,因为它们可以通过索引修改。

    int arr[5] = {1, 2, 3, 4, 5};
    arr[0] = 42; // arr[0] 是左值
    
  3. 引用: 引用也是左值,因为它们是变量的别名,可以用于修改原始变量的值。

    int y = 20;
    int& ref = y; // ref 是左值,可以修改 y 的值
    int && ref2 = std::move(y) // ref2 是左值
    ref = 30; // 修改了 y 的值
    
  4. 函数返回的左值: 如果函数返回的是一个具体的值(而不是临时对象或表达式的结果),那么它是左值。

    int getValue() {
        return 100;
    }
    int result = getValue(); // result 是左值
    

右值(Rvalue):

  • 纯右值 : 基本类型的常量或者临时对象

  • 将亡值: 自定义类型的临时对象

    右值是临时的、不可修改的值,通常表示计算结果或临时对象。右值是可以放在赋值运算符的右边的值。右值通常用于初始化变量或作为函数参数。

    1. 字面常量: 字面常量是右值,因为它们是固定的、不可修改的值。

      int a = 42; // 42 是右值
      
    2. 函数的返回值(临时对象): 当创建临时对象时,它们通常是右值。

      std::string getName() {
          return "Alice";
      }
      std::string greeting = "Hello, " + getName(); // "Hello, Alice" 是右值
      
    3. 表达式的返回值(临时对象): 复杂表达式的结果通常是右值,因为它们是计算的临时值。

      int sum = 5 + 3; // (5 + 3) 是右值
      
    4. std::move 返回的右值引用: std::move 函数将左值转换为右值引用,返回的是右值引用。

      int x = 42;
      int&& rvalueRef = std::move(x); // std::move(x) 是右值, rvalueRef是右值引用,但是本身为左值,可以修改x
      

    左值和右值在C++中的区分非常重要,因为它们在赋值、传递参数、函数返回值和移动语义等方面具有不同的语义。理解它们有助于编写更安全和高效的C++代码。

2 左值引用、右值引用

  1. 左值引用大部分引用左值
int a = 10;
int &b = a;
  1. const左值引用可以引用右值(常量)
const int &a = 10;
const int &b = x + y;
  1. 右值引用大部分引用右值
int &&a = 10;
int &&b = x + y;
  1. 通过move语义可以引用左值
int &&c = move(a);

3 比较

左值引用:

  1. 做参数:减少传参时的拷贝
  2. 做返回值:返回自身,减少拷贝

右值引用:

  • 用来写类的移动构造移动赋值,进而在类的使用过程中,减少拷贝次数
  1. 做参数:减少传参时的拷贝
  2. 函数内:通过调用移动构造,减少拷贝次数
  3. 做返回值:返回自身,减少拷贝
  4. 移动赋值、减少拷贝次数

image-20240225155254204.png

4 右值引用的应用(2个)

  1. 类的移动拷贝和移动赋值
class string
 {
 string(const char* str = "")
     :_size(strlen(str))
     , _capacity(_size)
     {
         //cout << "string(char* str)" << endl;
         _str = new char[_capacity + 1];
         strcpy(_str, str);
     }
 // s1.swap(s2)
 void swap(string& s)
 {
     ::swap(_str, s._str);
     ::swap(_size, s._size);
     ::swap(_capacity, s._capacity);
 }
 // 拷贝构造
 string(const string& s)
 	:_str(nullptr)
     {
         cout << "string(const string& s) -- 深拷贝" << endl;
         string tmp(s._str);
         swap(tmp);
     }
 // 赋值重载
 string& operator=(const string& s)
 {
     cout << "string& operator=(string s) -- 深拷贝" << endl;
     string tmp(s);
     swap(tmp);
     return *this;
 }
 // 移动构造
 string(string&& s)
     :_str(nullptr)
     ,_size(0)
     ,_capacity(0)
     {
         cout << "string(string&& s) -- 移动语义" << endl;
         swap(s);
     }
 // 移动赋值
 string& operator=(string&& s)
 {
     cout << "string& operator=(string&& s) -- 移动语义" << endl;
     swap(s);
     return *this;
 }
    • 模板父函数通过万能音乐和完美转发接收参数
    • 子函数函数重载 左值引用和右值引用
    // 模板父函数
    template<class T>
    void PushBack(T&& x)
    {
        //Insert(_head, x);
        Insert(_head, std::forward<T>(x));
    }
     void PushFront(T&& x)
     {
         //Insert(_head->_next, x);
         Insert(_head->_next, std::forward<T>(x));
     }
    
    // 子函数重载
     void Insert(Node* pos, T&& x)
     {
         Node* prev = pos->_prev;
         Node* newnode = new Node;
         newnode->_data = std::forward<T>(x); // 关键位置
         // prev newnode pos
         prev->_next = newnode;
         newnode->_prev = prev;
         newnode->_next = pos;
         pos->_prev = newnode;
     }
     void Insert(Node* pos, const T& x)
     {
         Node* prev = pos->_prev;
         Node* newnode = new Node;
         newnode->_data = x; // 关键位置
         // prev newnode pos
         prev->_next = newnode;
              newnode->_prev = prev;
         newnode->_next = pos;
         pos->_prev = newnode;
     }
    
    
    

5 万能引用和完美转发

万能引用是一种特殊类型的引用,它可以接受任意类型的值,包括左值和右值。

在C++中,万能引用通常使用&&符号来声明,就像右值引用一样。

万能引用通常与模板一起使用,例如:

template<typename T>
void foo(T&& t) {
    // ...
}

在这个例子中,T&&就是一个万能引用。这个函数模板foo接受一个参数t,它可能是左值也可能是右值。在函数体内部,你可以根据t的具体类型来决定如何处理它。

万能引用通常与模板推导结合使用,这意味着编译器会根据传递给函数的实参类型来推导出t的类型。如果传递给foo的是左值,T会被推导为左值引用类型;如果传递给foo的是右值,T会被推导为非引用类型。

完美转发:右值引用在第二次传参的过程中,右值属性会消失,因此需要完美转发 std::forward<T>(t)来保持属性不变

void Fun(int &x){ cout << "左值引用" << endl; }
void Fun(const int &x){ cout << "const 左值引用" << endl; }
void Fun(int &&x){ cout << "右值引用" << endl; }
void Fun(const int &&x){ cout << "const 右值引用" << endl; }

// 1 主函数,通过万能引用接收参数,自动推断为左右值引用
template<typename T>
void PerfectForward(T&& t)
{
    // 2 左值引用调用左值对应的函数,右值引用调用右值的函数,使用完美转发保持右值属性,否则属性失效,全部调用左值函数
 	Fun(std::forward<T>(t));
}
int main()
{
     PerfectForward(10);           // 右值
     int a;
     PerfectForward(a);            // 左值
     PerfectForward(std::move(a)); // 右值
     const int b = 8;
     PerfectForward(b);      // const 左值
     PerfectForward(std::move(b)); // const 右值
     return 0;
}