C++右值、右值引用、移动语义move、完美转发forward

右值、右值引用、移动语义move、完美转发forward

​ 内容参考:一文读懂C++右值引用和std::move - 知乎 (zhihu.com)

右值

左值可以取地址、位于等号左边;而右值没法取地址,位于等号右边。临时对象是右值

​ const左值引用不会修改指向值,因此可以指向右值,这也是为什么要使用const &作为函数参数的原因之一,如std::vectorpush_back

右值引用

​ 右值引用的标志是&&,顾名思义,右值引用专门为右值而生,可以指向右值,不能指向左值

int &&ref_a_right = 5; // ok
int a = 5;
int &&ref_a_left = a; // 编译不过,右值引用不可以指向左值
ref_a_right = 6; // 右值引用的用途:可以修改右值

移动语义move

​ 可以通过移动语义std:move使右值引用指向左值其实现等同于一个类型转换:static_cast<T&&>(lvalue),右值引用能指向右值,本质上也是把右值提升为一个左值,并定义一个右值引用通过std::move指向该左值:

int a = 5; // a是个左值
int &ref_a_left = a; // 左值引用指向左值
int &&ref_a_right = std::move(a); // 通过std::move将左值转化为右值,可以被右值引用指向
cout << a; // 打印结果:5
int &&ref_a = 5;
ref_a = 6;
等同于以下代码:
int temp = 5;
int &&ref_a = std::move(temp);
ref_a = 6;

​ 在实际场景中,右值引用和std::move被广泛用于在STL和自定义类中实现移动语义,避免拷贝,从而提升程序性能。 在拷贝构造和赋值重载函数中直接偷掉传入参数的资源,避免了数据的拷贝。

class Array {
public:
......
// 优雅
Array(Array&& temp_array) {
data_ = temp_array.data_;
size_ = temp_array.size_;
// 为防止temp_array析构时delete data,提前置空其data_
temp_array.data_ = nullptr;
}
public:
int *data_;
int size_;
};

示例如

// 例:std::vector和std::string的实际例子
int main() {
std::string str1 = "aacasxs";
std::vector<std::string> vec;
vec.push_back(str1); // 传统方法,copy
vec.push_back(std::move(str1)); // 调用移动语义的push_back方法,避免拷贝,str1会失去原有值,变成空字符串
vec.emplace_back(std::move(str1)); // emplace_back效果相同,str1会失去原有值
vec.emplace_back("axcsddcas"); // 当然可以直接接右值
}
// std::vector方法定义
void push_back (const value_type& val);
void push_back (value_type&& val);
void emplace_back (Args&&... args);

可移动对象在<需要拷贝且被拷贝者之后不再被需要>的场景,建议使用std::move触发移动语义,提升性能。

完美转发 std::forward

std::move一样,它的兄弟std::forward也充满了迷惑性,虽然名字含义是转发,但他并不会做转发,同样也是做类型转换.

与move相比,forward更强大,move只能转出来右值,forward左右值都可以。

std::forward(u)有两个参数:T与 u。 a. 当T为左值引用类型时,u将被转换为T类型的左值; b. 否则u将被转换为T类型右值。

举个例子,有main,A,B三个函数,调用关系为:main->A->B

void B(int&& ref_r) {
ref_r = 1;
}
// A、B的入参是右值引用
// 有名字的右值引用是左值,因此ref_r是左值
void A(int&& ref_r) {
B(ref_r); // 错误,B的入参是右值引用,需要接右值,ref_r是左值,编译失败
B(std::move(ref_r)); // ok,std::move把左值转为右值,编译通过
B(std::forward<int>(ref_r)); // ok,std::forward的T是int类型,属于条件b,因此会把ref_r转为右值
}
int main() {
int a = 5;
A(std::move(a));
}

例2

void change2(int&& ref_r) {
ref_r = 1;
}
void change3(int& ref_l) {
ref_l = 1;
}
// change的入参是右值引用
// 有名字的右值引用是 左值,因此ref_r是左值
void change(int&& ref_r) {
change2(ref_r); // 错误,change2的入参是右值引用,需要接右值,ref_r是左值,编译失败
change2(std::move(ref_r)); // ok,std::move把左值转为右值,编译通过
change2(std::forward<int &&>(ref_r)); // ok,std::forward的T是右值引用类型(int &&),符合条件b,因此u(ref_r)会被转换为右值,编译通过
change3(ref_r); // ok,change3的入参是左值引用,需要接左值,ref_r是左值,编译通过
change3(std::forward<int &>(ref_r)); // ok,std::forward的T是左值引用类型(int &),符合条件a,因此u(ref_r)会被转换为左值,编译通过
// 可见,forward可以把值转换为左值或者右值
}
int main() {
int a = 5;
change(std::move(a));
}

emplace

​ 在 C++11 之后,stl 容器中添加了新的方法:emplace_back() ,和 push_back() 一样的是都是在容器末尾添加一个新的元素进去,不同的是 emplace_back() 在效率上相比较于 push_back() 有了一定的提升。

​ 直接插入对象,emplace和push没有区别,但是给emplace传入对象构造所需要的参数,他会直接在容器底层构造对象,不在生成临时对象进行拷贝

v.emplace_back(10);
v.emplace_back(30, 40);

可变参函数模板

template<typename... Types>
void emplace_back(Types&&... args)
{
//不管左值引用还是右值引用,它本身是左值,传递的过程中要保持args的类型,需要完美转发
allocator_.construct(vec_ + idx_, forward<Types>(args...));
idx_++;
}
template<typename... Types>
void construct(T* ptr, Types&&... args)
{
new(ptr)T(std::forward<Types>(args)...);
}

引用折叠

所有的引用折叠最终都代表一个引用,要么是左值引用,要么是右值引用。

规则就是:

如果任一引用为左值引用,则结果为左值引用。否则(即两个都是右值引用),结果为右值引用。

也就是说,int& &&等价于int &

//引用折叠 实参:左值Test
template<typename... Types>
void emplace_back(Types&&... args)//万能引用
{ //Test&+&& -> Test&
}
posted @   霁空  阅读(123)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示