C++右值引用
2023-12-18 18:41:54
一、温故而知新
在温故过程中,对于C++重载overload的函数匹配优先级有这样一个先后顺序:
g++ v4.8.1匹配规则;
完全匹配>常量匹配>升级转换>降级转换>省略号匹配
然后为了验证这个问题,我写了如下的示例代码:
#include <sal.h>
#define MYTRACE() do { std::cout << __FUNCTION__ << ",line" << __LINE__ << std::endl;}while(0)
class FnOverWrite
{
public:
void ooxx(const int& a,const int& b){ MYTRACE();}
void ooxx(const int&& a,const int&& b){MYTRACE();}
void ooxx(int a,double b){MYTRACE();}
void ooxx(double a,int b){MYTRACE();}
};
int main()
{
FnOverWrite fow;
int num1 = 3,num2 = 4;
double d3 = 12.12,d4=0.001;
// 调用左值引常用重载函数 ==》 void ooxx(const int& a,const int& b){ MYTRACE();}
fow.ooxx(num1,num2);
// 调用右值常引用重载函数 ==》void ooxx(const int&& a,const int&& b){MYTRACE();}
fow.ooxx(int(3),int(4));
fow.ooxx(num1,d3); /// 调用完全匹配 ==》 void ooxx(int a,double b){MYTRACE();}
fow.ooxx(d3,(int)d4); /// 编译器傻眼,必须显示转换 ==》void ooxx(double a,int b){MYTRACE();}
return 0;
}
那么,提个小问题,为什么fow.ooxx(int(3),int(4));
调用的是右值引用?
二、浅尝辄止
右值引用是C++11引入的一个新特性,主要用于实现移动语义和完美转发。它有以下优点和缺点:
优点:
- 实现移动语义:右值引用可以绑定到将要销毁的对象(例如临时对象或者使用std::move转换的对象)。这使得我们可以安全地“窃取”这些对象的资源,而不是复制它们。这可以大大提高性能,特别是对于大型对象。
- 实现完美转发:使用右值引用,我们可以在模板函数中保持参数的原始类型(包括左值、右值、常量等属性)不变地传递给其他函数。这被称为完美转发。
缺点:
- 代码复杂性:使用右值引用需要对C++的值类别有深入的理解。否则,可能会导致代码难以理解和维护。
- 潜在的错误:如果不正确地使用右值引用,可能会导致难以发现的错误。例如,如果你从一个函数返回一个右值引用,那么返回的引用可能会引用一个已经销毁的对象。
- 兼容性问题:右值引用是C++11引入的特性,不被旧版本的C++标准支持。如果你的代码需要在旧版本的C++编译器上运行,那么你不能使用右值引用。
注意事项:
在使用右值引用时,你需要注意以下几点:
- 不要返回局部对象的右值引用:如果你从一个函数返回一个局部对象的右值引用,那么这个引用将会引用一个已经销毁的对象,这是未定义行为。
- 谨慎使用 std::move:std::move 可以将一个左值转换为右值,从而触发移动语义。然而,一旦一个对象被 std::move,你就不能再使用这个对象了,除非你可以确定这个对象的状态。
- 理解值类别:在使用右值引用时,你需要理解C++的值类别,包括左值、右值、纯右值(prvalue)和将亡值(xvalue)。这将帮助你理解何时应该使用右值引用,以及右值引用如何工作。
- 理解移动语义和完美转发:移动语义和完美转发是右值引用的两个主要应用。你需要理解这两个概念,以及如何在你的代码中使用它们。
三、有的放矢
1、实现移动语义
#include <iostream>
#include <vector>
int main() {
std::vector<int> v1 = {1, 2, 3, 4, 5};
std::vector<int> v2 = std::move(v1); // 使用右值引用实现移动语义
std::cout << "v1.size(): " << v1.size() << std::endl; // 输出:v1.size(): 0
std::cout << "v2.size(): " << v2.size() << std::endl; // 输出:v2.size(): 5
return 0;
}
在这个例子中,std::move(v1)
返回一个右值引用,这个右值引用绑定到 v1
。然后,v2
的构造函数接收这个右值引用,并“窃取” v1
的资源,而不是复制它们。
2、实现完美转发
#include <iostream>
#include <utility>
void foo(int& x) { std::cout << "lvalue\n"; }
void foo(int&& x) { std::cout << "rvalue\n"; }
template <typename T>
void bar(T&& x) {
foo(std::forward<T>(x)); // 使用右值引用实现完美转发
}
int main() {
int i = 0;
bar(i); // 输出:lvalue
bar(0); // 输出:rvalue
return 0;
}
在这个例子中,bar 函数接受一个右值引用参数,然后使用 std::forward(x) 将这个参数完美地转发给 foo 函数。
3、增加代码的复杂性
#include <iostream>
#include <utility>
template <typename T>
void swap(T& a, T& b) {
T tmp(std::move(a)); // 使用右值引用
a = std::move(b); // 使用右值引用
b = std::move(tmp); // 使用右值引用
}
int main() {
int x = 1;
int y = 2;
swap(x, y);
std::cout << "x: " << x << ", y: " << y << std::endl; // 输出:x: 2, y: 1
return 0;
}
4、潜在的错误:不要返回局部对象的右值引用
#include <iostream>
int&& dangerous() {
int x = 123;
return std::move(x); // 错误:返回对局部变量的右值引用
}
int main() {
int&& r = dangerous();
std::cout << r << std::endl; // 未定义行为:r 引用了一个已经销毁的对象
return 0;
}
在这个例子中,dangerous 函数返回一个对局部变量 x 的右值引用。当 dangerous 函数返回后,x 就被销毁了,所以 r 引用了一个已经销毁的对象,这是未定义行为。
5、 兼容性问题:
涉及到编译器和C++标准的版本。如果你的代码需要在旧版本的C++编译器上运行,那么你不能使用右值引用。
文章来源:https://blog.csdn.net/weixin_39568531/article/details/135063489
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!