关于右值引用与完美转发(还加入了一些补充,涉及到cpp对象的优化)

今日的疑问: 在下面的代码中,forwardFunction(n); // 为什么这里调用的是process(int&),而不是process(int&&)?

#include <iostream>
#include <utility>

void process(int &x)
{
    std::cout << "process(int& x):";
    std::cout << "process called with lvalue: " << x << std::endl;
}

void process(int &&x)
{
    std::cout << "process(int&& x):";
    std::cout << "process called with rvalue: " << x << std::endl;
}

template <typename T>
void forwardFunction(T &&arg)
{
    process(std::forward<T>(arg)); // 转发参数时保持原始值类别
}

int main()
{
    int x = 10;
    int &m = x;         // 定义一个左值引用变量
    int &&n = 10;       // 定义一个右值引用变量
    forwardFunction(m); // 调用 process(int&),传递一个左值引用
    forwardFunction(n); // 为什么这里调用的是process(int&),而不是process(int&&)?

    return 0;
}

解答:

在上面的代码中,forwardFunction(n) 调用的是 process(int&),而不是 process(int&&),
是因为: n 是一个右值引用变量,但它本身是一个左值。
右值引用变量可以绑定到一个右值,但它本身不是一个右值,而是一个具有名字的对象,可以被多次访问。
因此,当你将 n 传递给 forwardFunction 时,它会被推导为 int& 类型,而不是 int&& 类型。如果您想让 n 被当作右值传递,您需要使用 std::move(n) 来转换它。

std::forward 的作用是保持参数的原始值类别,即如果输入的参数是左值,那么传递给下一个函数的参数的也是左值;如果输入的参数是右值,那么传递给下一个函数的参数的也是右值1。这样可以实现完美转发,即根据参数的不同类型调用不同的重载函数。

想要使用右值的方法有很多,如下:

在这里插入图片描述

补充:

  • 一个右值引用变量本身还是一个左值

  • 经过测试:当我们把一个右值引用变量“直接传入”模板函数,调用的是左值引用;当我们把一个右值引用变量“通过std::forward()传入”模板函数,调用的是右值引用

template<typename T>
void construct(T *p,const T &val)//负责对象构造
{
	new (p) T(val); //是左值引用
}
void construct(T *p,T &&val)
{
	new (p) T(std::move(val));//是右值引用
}



//使用完美转发,就可以替换掉上面两个函数(也就是说下面这个函数,一个顶俩)
template<typename Ty>
void construct(Ty *p,Ty &&val)
{
	new (p) Ty(   std::forward<Ty>(val)   );
}
  • 左值:有内容有名字 ; 右值:没内存没名字(临时量)
  • 右值引用或者常引用 可以引用右值
const int &c = 20;//常引用
int &&d = 20; //右值引用
  • 关于cpp中的一个优化(重点!!!)
/*
cpp编译器对于对象构造的优化:用临时对象生产新对象的时候,
临时对象就不产生了,直接构造新对象就可以了(也就是说初始化的时候)
*/
class Test{};
Test t4 = Test(20);//和Test t4(20);没有区别
  • 函数调用,将实参传给形参,是初始化还是赋值函数?

answer:是初始化.
既然是初始化,就要调用构造函数(或者拷贝构造)

Test GetObject(Test t)//3.Test(const Test &)//实参传给形参会调用拷贝构造
{
    int val = t.getData();
    Test tmp(val);//4.Test(Test &)
    return tmp;//5.Test(const Test &)因为tmp是临时变量,所以无法带出GetObject这个函数,所以要创建一个临时变量,所以以tmp为原型再拷贝构造一个临时变量
}//6.~Test() tmp进行析构
//7.~Test() 形参t要析构

int main()
{
    Test t1;//1. Test(int)
    Test t2;//t2.Test(int)  
    t2.GetObject(t1);//8.operator=
    //9.~Test()临时变量要析构
    //10.~Test()t1析构
    //11.~Test()t2析构

    return 0;
}
  • 举个例子,大家想想下面的代码中,构造与析构的顺序是什么?
  1. 例子一
Test Getobject(Test &t)//因为是引用传递,所以没有调用拷贝构造
{
    int val = t.getData();
    /*Test tmp(val)
    return tmp;*/
    return Test(val);//返回临时对象
}
int main()
{
    Test t1;
    Test t2;
    t2 = Getobject(t1);//临时对象在赋值给t2后,调用析构函数(不是初始化)
    return 0;
}


1.构造
2.构造
3.拷贝构造(临时对象)
4.赋值
5.析构(临时对象)
6.析构
7.析构
  1. 例子二
Test Getobject(Test &t)
{
    int val = t.getData();
    /*Test tmp(val)
    return tmp;*/
    return Test(val);//返回临时对象
}
int main()
{
    Test t1;
    Test t2 = Getobject(t1);//用临时对象在初始化新的对象,临时对象就会被优化掉(是初始化)
    return 0;
}


1.构造
2.构造(直接构造t2对象,没有调用拷贝构造了【或者说拷贝构造被优化了!!!】)

3.析构
4.析构

下面是一些大佬总结的《cpp对象优化的三条原则》

1.函数参数传递过程中,对象优化按引用传递,不要值传递
2.函数返回对象的时候,应该优先返回一个临时对象,而不要返回一个定义过的对象
3.接受返回值是对象的函数调用的时候,优先按初始化的方式接收,不要按赋值的方式接收

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

踏过山河,踏过海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值