C/C++开发基础——移动语义和右值引用

2024-02-05 17:49:58 浏览数 (1)

一,右值的基本概念

左值是可以被获取地址的变量,经常出现在赋值语句的左边。

不属于左值的变量都是右值变量,经常出现在赋值语句的右边,例如:字面量,临时对象,临时值。

有名称的变量是左值,没有名称的变量比如"3 4"是右值。

从生命周期看,左值变量是一个持久的变量,在代码运行期间会一直存在,右值变量是一个临时的变量,在代码运行期间会被释放。

右值变量之所以是临时的,是因为一些字面量,表达式计算出来的中间变量、临时对象等都是临时的,使用完就要被销毁。

举个例子:

代码语言:javascript复制
int a = 40

a:左值,可以获取a的地址:&a。
40:整型字面量,是个临时值,右值变量,不能被获取地址,编码时不能写&40。

二,右值引用的基本概念

右值引用,其实就是字面上说的,针对右值变量的引用。

引用的含义和别名差不多,左值引用通常被理解为左值变量的别名,那么右值引用也可以被理解为右值变量的别名。

右值引用,只针对特别的右值变量,比如临时对象,而字面量等形式的右值变量依旧无法被引用。

大多数情况下,右值引用只能绑定到一个将要被销毁的对象上。

右值引用还可以引用一个临时的表达式结果,只要右值引用还在作用域内,那么这个临时变量就不会被马上释放。因此,右值引用的使用,可以延长临时变量的生命周期。

右值引用在函数参数中的表现形式为:

代码语言:javascript复制
type_name&& var_name

右值引用和左值引用本质上都是引用,但是右值引用要表达的意思是被引用对象的值在使用结束后大概率会被释放,表明了引用的是临时值。

举个例子:

代码语言:javascript复制
int m=5;
int&& n=m 3;

m 3: 右值
&&n: 右值应用,表达式(m 3)的临时结果的别名

代码样例:

代码语言:javascript复制
#include <string>
#include <future>
#include <iostream>

using namespace std;

void handleMessage(std::string& msg)
{
    std::cout << "Handle Message with lvalue reference." << msg << std::endl;
}
void handleMessage(std::string&& msg)
{
    std::cout << "Handle Message with rvalue reference." << msg << std::endl;
}

int main() {
    std::string a = "Hello";
    handleMessage(a);
    handleMessage((std::string)"Hello"   (std::string)" World.");
    return 0;
}

运行结果:

代码语言:javascript复制
Handle Message with lvalue reference.Hello
Handle Message with rvalue reference.Hello World.

三,移动语义

在C 11之前,主要通过引用或指针来替换传值操作,为了避免在传参过程中,产生不必要的复制操作,在C 11标准中引入了移动语义,使一个对象不仅可以被复制,还可以被移动。

移动语义是指:将资源从一个对象转移到另一个对象,原有对象的资源被释放。

移动语义是基于右值引用来实现的。

移动语义是为了处理或传递一个临时变量的值。

使用移动语义需要避免使用const关键字,const关键字可以使临时变量常量化,成为一个常量右值,从而无法使用移动语义。

C 11标准引入右值引用的目的是提高代码的运行速率,提高的方式是将复制对象的操作改为移动对象。

针对对象的移动语义需要有:

1.移动构造函数

2.移动赋值运算符

移动构造函数和移动赋值运算符的参数都是右值引用"&&"类型。

C 标准库提供了移动语义相关的函数接口:std::move()。

std::move()的处理方式类似于强制类型转换,它可以将左值转换为右值。

以下代码可以实现一个仿制的std::move()

代码语言:javascript复制
template <typename T>
T&& move(T& x) noexcept {return static_cast<T&&>(x);}

四,完美转发

完美转发的含义:参数在函数模板之间传递时保持类型不被改变。

完美转发的作用相当于一个函数包装器。

C 标准库提供了用于完美转发的函数接口:std::forward()。

完美转发不改变变量的左右值属性,如果变量是左值,传入给std::forward处理后该变量还是左值。

代码样例:

代码语言:javascript复制
#include <string>
#include <iostream>

void RunCode(int&& m) { 
    std::cout << "rvalue ref." << std::endl; 
}
void RunCode(int& m) { 
    std::cout << "lvalue ref." << std::endl; 
}
void RunCode(const int&& m) { 
    std::cout << "const rvalue ref." << std::endl; 
}
void RunCode(const int& m) { 
    std::cout << "const lvalue ref." << std::endl; 
}

template <typename T>
void PerfectForward(T&& t) { 
    RunCode(std::forward<T>(t)); 
}

int main() {
    int a;
    int b;
    const int c = 1;
    const int d = 0;
    PerfectForward(a);
    PerfectForward(std::move(b));
    PerfectForward(c);
    PerfectForward(std::move(d));
    return 0;
}

运行结果:

代码语言:javascript复制
lvalue ref.
rvalue ref.
const lvalue ref.
const rvalue ref.

0 人点赞