> 文章列表 > c++ 左值 广义左值 右值 纯右值 将亡值

c++ 左值 广义左值 右值 纯右值 将亡值

c++ 左值 广义左值 右值 纯右值 将亡值

  • 为什么C/C++等少数编程语言要区分左右值?
  1. 历史原因: C语言作为一门古老的编程语言,其设计初衷是为了在硬件资源有限的系统上进行高效的编程,因此其语法和语义设计相对较简单。左值和右值的概念最初是由C语言引入的,而C++则在此基础上进行了扩展。这种设计历史原因导致了C/C++等语言中需要区分左值和右值。

  2. 内存管理: C/C++是一种比较底层的编程语言,直接操作内存。左值通常对应于具名变量,它们在内存中有固定的存储位置,可以被多次引用。而右值通常对应于临时的表达式或者字面值,它们在内存中没有固定的存储位置,只是临时的值。区分左值和右值有助于编译器在内存管理方面做出合适的优化,例如对右值进行临时对象的优化,避免不必要的内存拷贝。

  3. 语法规则: C/C++等语言中有许多操作只能对左值或只能对右值进行,例如赋值语句中等号左边必须是左值,而函数调用中实参必须是右值。如果没有左值和右值的区分,这些语法规则就无法定义和解释。这种语义上的区别使得编程语言可以在语法层面对不同类型的表达式和变量进行限制和处理。

  4. 性能优化: 区分左值和右值有助于编译器进行性能优化。例如,C++11引入的移动语义(Move Semantics)就是利用了右值的特性,通过将临时对象的资源(如内存)直接转移给目标对象,避免了不必要的拷贝操作,从而提高了性能。

总的来说,C/C++等语言区分左值和右值是为了在内存管理、语义和性能优化等方面提供更多的灵活性和效率。然而,对于初学者来说,理解和使用左值和右值可能会有一定的难度,因此在编写代码时需要仔细考虑它们的语法和语义规则。

  • 对于方法来说,传递左值或右值作为参数各有不同的用途和行为:
    • 传递左值:将左值传递给方法,方法可以对其进行修改,并且对传递的左值的修改会在函数调用后保持。这对于需要在方法内部修改传递的对象,并且希望在函数调用后对原对象产生影响的情况非常有用。
    • 传递右值:将右值传递给方法,方法可以从其获取值或者资源,并在方法内部进行处理,例如移动语义(move semantics)的情况下,可以将右值的资源转移到方法内部,从而避免不必要的复制操作。传递右值还可以用于实现完美转发(perfect forwarding),将右值引用传递给下游函数。

在一般情况下,对于方法的入参:
如果方法需要修改传递的对象或者希望在函数调用后对原对象产生影响,则传递左值作为参数。
如果方法只需要获取传递的值或者资源,并且不需要修改传递的对象,或者希望实现移动语义或完美转发,则传递右值作为参数。这可以根据方法的具体需求和语义来进行选择。

需要注意的是,在 C++11 及之后的版本中,通过使用引用折叠规则(reference collapsing rules)和右值引用(rvalue reference),可以实现在方法中同时接收左值和右值的参数,从而在函数调用时不需要显式地区分左值和右值。这可以提供更加灵活和高效的参数传递方式。

表达式: 由运算符和运算对象构成的计算式。字面量、变量、函数返回值都是表达式。 表达式返回的结果,有两个属性:类型和值类别。

左值(lvalue)

  • 可以出现在赋值运算符左边的表达式。可以取地址的表达式
  • 可以通过 ‘&’ 取到左值的地址
  • 可修改的左值可用作 ‘=’ 的左操作数
  • 可用于初始化左值引用
void modifyLeftValue(int& x) {x = x + 1; // 修改传递的左值
}int main(){int num = 10;modifyLeftValue(num); // 传递左值给方法std::cout << num << std::endl; // 输出 11,因为方法内部修改了传递的左值return 0;
}
//变量:包括普通变量、引用(包括左值引用和常量引用)以及指针。
int a = 10;        // 普通变量
int& b = a;       // 左值引用
const int& c = a; // 常量引用
int* ptr = &a;    // 指针//数组元素:数组的元素可以通过索引访问,并且可以用作左值。
int arr[5] = {1, 2, 3, 4, 5};
arr[0] = 10; // 数组元素作为左值//类成员:类的成员变量可以作为左值,包括普通成员变量和引用类型的成员变量。
class MyClass {
public:int x;int& y;MyClass(int a, int b) : x(a), y(b) {}
};MyClass obj(1, 2);
obj.x = 10; // 类成员变量作为左值
obj.y = 20; // 类引用成员变量作为左值//解引用指针:解引用指针可以得到一个左值。
int a = 10;
int* ptr = &a;
*ptr = 20; // 解引用指针作为左值//函数返回的引用:如果一个函数返回一个引用类型的值,那么该返回值可以作为左值。
int& getReference() {static int x = 10;return x;
}
getReference() = 30; // 函数返回的引用作为左值

常见的左值表达式包括:

  • 变量,对象。例如, int i
  • 返回类型为左值引用的函数调用或重载运算符表达式,例如 str1 = str2、++it
  • 所有内建的赋值及复合赋值表达式,例如 a = b、a += b
  • 内建的前置自增、前置自减,例如 ++i、–i
  • 内建的间接寻址表达式,例如 *p
  • 字符串字面量,例如 “hello world”
  • 内建的下标表达式, 例如 a[n]
  • 迭代器是左值,例如 vecotr::iterator iter
//常量(Constants):常量是无法改变的值,例如整数、浮点数等。尽管它们在语法上可以被视为左值,但它们不能作为左值引用的目标,因此不是广义左值。
const int a = 10;
a = 20; // 错误,a 是左值但不是广义左值
//数组(Arrays):数组在语法上可以被视为左值,但不能作为左值引用的目标,因此不是广义左值。
int arr[5];
arr = nullptr; // 错误,arr 是左值但不是广义左值

广义左值(Generalized lvalue)

  • 指可以出现在赋值运算符左边的表达式,包括左值(lvalue)和一些具有类似于左值性质的表达式。C++11 标准引入了广义左值的概念,扩展了左值的范围,使得一些原本不能作为左值的表达式也可以用于赋值操作。
  • 广义左值并不是所有可以出现在赋值运算符左边的表达式都可以作为左值使用,因为它们仍然受到语言规则和类型系统的限制。广义左值的引入主要是为了支持更灵活的赋值和修改操作,提供更方便的编程方式。
//临时对象(Temporary objects):临时对象是由表达式生成的临时对象,例如函数返回的临时对象。虽然临时对象在语法上可以被视为左值,但它们不能作为左值引用的目标,因此不是真正的左值。
int foo() { return 42; }
foo() = 10; // 错误,foo() 是广义左值但不是左值//表达式的结果(Result of an expression):一些表达式的结果可以被视为广义左值,例如赋值操作符(=)的返回值。尽管它们在语法上可以被视为左值,但它们不能作为左值引用的目标,因此不是真正的左值。
int a = 10;
int b = 20;
(a + b) = 30; // 错误,(a + b) 是广义左值但不是左值

右值(rvalue)

  • 指临时的、不可取地址的表达式;可以通过使用双 ampersand (&&) 作为引用类型来实现。
  • 右值引用允许将临时对象(右值)绑定到引用上,并在方法中修改它们。C++ 的语言规范,对于右值引用绑定的临时对象,其生命周期通常在方法退出后结束,因此在方法退出后,对这个临时对象的修改可能会导致未定义行为。
  • 右值引用的设计初衷是用于优化性能,例如通过避免不必要的对象复制
  • 要避免对右值引用绑定的临时对象进行修改后导致未定义行为,可以使用 std::move 函数将右值引用转换为左值引用,从而使得在方法中修改的是原始对象而不是临时对象。
void processRightValue(std::string&& str) {// Processing: Hello , str : 0x308c32280std::cout << "Processing: " << str <<" , str : "<<&str<< std::endl; // 使用传递的右值
}int main(){processRightValue("Hello"); // 传递右值给方法// 注意:传递的右值字符串在方法调用后会被销毁,因为它是临时的,不可取地址的表达式return 0;
}//void processRightValue(std::string&& str) {std::cout << "Processing: " << str <<" , str : "<<&str<< std::endl;str = "xxx";
}int main(){std::string val = "hello";//std::move(val) 和 std::forward<std::string>(val) 传递进去的值被修改的调用后 都能保留processRightValue(std::move(val));std::cout<<"--val : "<<val<<std::endl;//xxxreturn 0;
}

纯右值(Pure Right-hand-side Value)

  • 是一种表达式类型,通常可以作为右值引用(Rvalue Reference)的绑定目标。纯右值是指在表达式求值后不再被使用的临时对象,它可以被移动语义(Move Semantics)优化,从而避免不必要的拷贝操作,提高代码效率。
//临时对象:例如通过调用函数返回值、执行类型转换、进行算术运算等产生的临时对象都可以是纯右值。
int x = 1 + 2; // 表达式 1 + 2 是纯右值//字面量:例如整型、浮点型、字符型等字面量常量都可以是纯右值。
字面量:例如整型、浮点型、字符型等字面量常量都可以是纯右值。//强制类型转换产生的临时对象:例如使用 static_cast、dynamic_cast、const_cast、reinterpret_cast 等强制类型转换操作时,产生的临时对象可以是纯右值。
int z = static_cast<int>(3.14); // 强制类型转换产生的临时对象是纯右值//在 C++11 标准引入右值引用之后,纯右值的概念扩展了,包括了具有右值引用类型的表达式也可以被视为纯右值。
int a = 1;
int&& rvalue_ref = std::move(a); // 右值引用是纯右值

纯右值和右值在实际使用中可能有一些细微的区别和限制,具体取决于编译器和语言标准的实现。

  • 纯右值和右值之间存在以下几个区别:
    • 定义:纯右值是指在表达式求值后不再被使用的临时对象,可以作为右值引用(Rvalue Reference)的绑定目标。而右值是指在表达式求值后,其值可以被移动(Move)但不可以被复制(Copy)的表达式,包括纯右值和具有右值引用类型的表达式。
    • 生命周期:纯右值的生命周期较短,通常仅在表达式求值期间有效,并且无法被持久化。而右值则可能具有更长的生命周期,例如通过右值引用延长其生命周期。
    • 可用性:纯右值通常在表达式求值后不再可用,不能再进行其他操作,因为它们是临时对象。而右值可能具有更长的可用性,例如通过右值引用继续使用它们。
    • 移动语义:纯右值在进行赋值或传递时,可以通过移动语义(Move Semantics)进行高效的资源转移,避免不必要的拷贝操作。而右值也可以通过移动语义进行资源转移,但具有更广泛的适用性,包括纯右值和具有右值引用类型的表达式。
    • 使用场景:纯右值主要用于临时对象的创建和传递,例如通过函数返回值、执行类型转换、进行算术运算等产生的临时对象。而右值则可以更广泛地用于各种场景,包括作为函数参数、返回值、成员变量、局部变量等。右值引用还可以用于实现移动语义、完美转发等高效的 C++ 特性。

将亡值(Expiring Value)

  • 亡值实际上是 C++ 11 引入了右值引用的概念而引入的,在C++ 11 之前,右值可以等价于纯右值。 亡值与右值引用息息相关。
  • 将亡值指即将被销毁的临时对象或右值引用。C++11引入了将亡值的概念,它是一种介于左值和纯右值之间的临时对象。
    *
  • 需要注意的是,将亡值在转移资源所有权的同时,会让原始对象变为无效状态。因此,在使用将亡值时需要小心,确保不会在对象变为无效状态后再次访问它。同时,也需要了解不同编译器对将亡值的优化行为可能有所不同,因此在编写跨平台或可移植性较强的代码时需要谨慎使用将亡值。

将亡值主要用于在C++11中引入的移动语义,允许将资源(如内存或文件句柄)从一个对象转移到另一个对象,而不进行昂贵的资源拷贝操作。将亡值通常出现在以下两种情况下:

  1. 将亡值作为函数返回值:当函数返回一个临时对象时,C++编译器可以将其优化为将亡值,从而避免不必要的拷贝操作,提高性能。
std::string get_string() {// 返回一个临时对象,将亡值return std::string("Hello");
}std::string str = get_string(); // 将亡值转移到str中
  1. 将亡值作为函数参数:当将一个临时对象传递给函数时,C++编译器可以将其优化为将亡值,从而避免不必要的拷贝操作,提高性能。
void process_string(std::string&& str) {// 处理str,这里str是将亡值
}std::string str = "Hello";
process_string(std::move(str)); // 将亡值转移到process_string函数中

CG素材网