左值右值 右值引用

Posted on 2023-09-08 11:29  金色的省略号  阅读(36)  评论(0编辑  收藏  举报

  一、左值与右值

  左值 与 右值是C++中表达式的属性在C++11中,每个表达式有两个属性: 类型(type,除去引用特性,用于类型检查)和 值类型(value category,用于语法检查,比如一个表达式结果是否能被赋值)。

  值类型包括3个基本类型:lvalue、prvalue与xrvalue,后两者又统称为rvalue,lvalue我们称为左值,可以将左值看成是一个可以获取地址的量,它可以用来标识一个对象或函数,rvalue称为右值,可以认为所有不是左值的量就是右值,prvalue就是纯粹的右值,比如字面量,xrvalue指的是可以被重用的临时对象。

  二、右值引用

  在C++98中,临时量( 术语为右值,因其出现在赋值表达式的右边 )可以被传给函数但只能被接受为const &类型。这样函数便 无法区分传给const &的是真实的右值还是常规变量,而且,由于类型为const &,函数也无法改变所传对象的值

  在C++11引入右值引用,记作typename &&,这种类型 可以被接受为非const值从而允许改变其值

  右值引用 是C++11引入的一个非常重要的技术,因为它是移动语义(Move semantics)与完美转发(Perfect forwarding)的基石:

  移动语义:将内存的所有权从一个对象转移到另外一个对象,高效的移动用来替换效率低下的复制,对象的移动语义需要实现移动构造函数(move constructor)和移动赋值运算符(move assignment operator)

#include <iostream>
#include <string>
#include <utility> 

int main ()
{ 
    std::string &&rr =  "World";
    {
        std::string s = "Hello";
        rr =  std::move(s ); // rr获取s的资源 //把一个左值移到为右值引用
        s = "!!!";
        //std::cout << s; 
        /* 请注意,移动意味着被移出的对象保持在有效但未指定的状态。
        这意味着,在这样一个操作之后,移出对象的值只应该被销毁或赋一个新值;
        否则访问它将产生一个未指定的值。 */        
    }    
    std::cout << rr;
    
    return 0;
}
View Code
// move example
#include <utility>      // std::move
#include <iostream>     // std::cout
#include <vector>       // std::vector
#include <string>       // std::string

int main () {
  std::string foo = "foo-string";
  std::string bar = "bar-string";
  std::vector<std::string> myvector;

  myvector.push_back (foo);                    // copies
  myvector.push_back (std::move(bar));         // moves

  std::cout << "myvector contains:";
  for (std::string& x:myvector) std::cout << ' ' << x;
  std::cout << '\n';

  return 0;
}
View Code

  完美转发:定义一个函数模板该函数模板可以接收任意类型参数,然后将参数转发给其它目标函数,且保证 目标函数接受的参数其类型与传递给模板函数的类型相同。 

// forward example
#include <utility>      // std::forward
#include <iostream>     // std::cout

// function with lvalue and rvalue reference overloads:
void overloaded (const int& x) {std::cout << "[lvalue]";}
void overloaded (int&& x) {std::cout << "[rvalue]";}

// function template taking rvalue reference to deduced type:
template <class T> void fn (T&& x) {
  overloaded (x);                   // always an lvalue
  overloaded (std::forward<T>(x));  // rvalue if argument is rvalue
}

int main () {
  int a;

  std::cout << "calling fn with lvalue: ";
  fn (a);      //如果参数a是左值引用,则该函数返回类型不变的a // [lvalue][lvalue]
  std::cout << '\n';

  std::cout << "calling fn with rvalue: ";
  fn (0);  //否则,函数返回一个右值引用(T&&),该引用指向可用于传递右值的参数//[lvalue][rvalue]
  std::cout << '\n';

  return 0;
}
View Code
#include <utility> 
#include <iostream> 
#include <type_traits> //这个头文件定义了一系列在编译时获取类型信息的类 

/* void overloaded_fn(int& x){ //左值引用
    x = 111;
}

void overloaded_fn(int&& x){//右值引用
    x = 222;
} */

template<typename T>
void overloaded_fn(T&& x){
    x = 888;
}

template<typename T> // 实参为左值,T 为 int& //实参为右值,T为int
void f( T&& x){      //T 为 int&,函数参数引用折叠//int& &&   int&
    //is_same是标识T是否与U相同类型的Trait类
    if(std::is_same<T, int& >::value ) std::cout << "int&";        
    if(std::is_same<T, int  >::value ) std::cout << "int";
    overloaded_fn(std::forward<T>(x) ); //左值转发左值,右值转发右值// 完美转发    
}

/* using T = int&;
void f( T&& x ){   //引用折叠
    std::cout << "void f( int&& x)\n";
} */

int main () 
{
  int a = 333; 
  f( a );
  //f(std::move(a) ); 
  std::cout << a;

  return 0;
}
View Code

  std::forward保持实参的类型,保持实参的左值/右值属性

#include <utility> 
#include <iostream> 
#include <type_traits> 

void overloaded_fn(const int& x){ //左值引用
    std::cout << "void overloaded_fn(const int& x)";
}

 void overloaded_fn(int&& x){    //右值引用
    std::cout << "void overloaded_fn(int&& x)";
}

template<typename T> //实参的所有性质(T),包括实参是否是const的以及实参是左值还是右值 
void f( T&& x){  
    //is_same是标识T是否与U相同类型的Trait类
    if(std::is_same<T, int& >::value ) std::cout << "int&, "; 
    if(std::is_same<T, const int& >::value ) std::cout << "const int&, ";    
    if(std::is_same<T, int  >::value ) std::cout << "int, ";
    overloaded_fn( std::forward<T>(x) ); //右值,不用forward,会匹配 void overloaded_fn(const int& x)
    //std::forward保持实参的类型,保持实参的左值/右值属性
}

int main () 
{
  const int a = 333; 
  f( 15 );  //右值
  //f(a );  //左值 const int&

  return 0;
}
View Code

  可变参数模板与完美转发

#include <utility>
#include <iostream>

template<typename Func, typename... Args>
void test(Func f, Args&&... args ) 
{
    f(std::forward<Args>(args)... );
}

void func(int a, int &b){ 
    a++;
    b++;
}

void func(int &a, int &b){ 
    a++;
    b++;
}

int main ()
{ 
    int a=3;
    int b=5;
    
    using F1 = void (*)(int,int& );
    using F2 = void (*)(int&,int&);
    F1 f1 = &func;
    F2 f2 = &func;
    
    //test(f1, a, b );  //传左值
    //test(f1, 15, b ); //传右值、左值
    test(f2, a, b );    //传左值

    std:: cout << a << "," << b;
    
    return 0;
}
View Code