C++完美转发解析

news/2024/11/7 21:17:51/

1. 完美转发的概念

完美转发(Perfect Forwarding)是 C++11 中引入的一种编程技巧,其目的是在编写泛型函数时能够保留参数的类型和值类别(左值或右值),从而实现更为高效且准确地传递参数。通过使用右值引用和模板类型推导,完美转发允许我们在函数中以原始参数的形式将参数传递给其他函数,而不会发生不必要的拷贝操作,从而提高性能。

2. 左值与左值引用

左值是表达式的一种属性,表示可以出现在赋值运算符左侧的值。左值引用是 C++ 中传统的引用类型,用符号 ‘&’ 表示。左值引用可以绑定到左值,从而实现对左值的引用和修改。例如:

int x = 10;
int& ref_x = x; // 左值引用绑定到左值 x
ref_x = 20; // 通过引用修改 x 的值
1.3 右值与右值引用
右值是指不能出现在赋值运算符左侧的表达式,通常表示临时对象或即将被销毁的对象。C++11 引入了右值引用,用符号 ‘&&’ 表示。右值引用可以绑定到右值,从而实现对右值的引用和修改。例如:

int&& ref_rv = 42; // 右值引用绑定到一个临时整数对象
ref_rv = 55; // 通过引用修改右值
2.3 std::move 的作用
std::move 是一个将左值转换为右值引用的工具,它可以让我们在需要时将左值当作右值使用。例如,我们可以使用 std::move 实现对象的移动语义,从而避免不必要的拷贝操作。例如:

std::vector v1 = {1, 2, 3};
std::vector v2 = std::move(v1); // 使用 std::move 将 v1 的资源移动到 v2,避免了拷贝操作

3. 万能引用

C++ 11中有万能引用(Universal Reference)的概念:使用​​T&&​​类型的形参既能绑定右值,又能绑定左值。

但是注意了:只有发生类型推导的时候,T&&才表示万能引用;否则,表示右值引用。

4. forward的作用

std::forward被称为完美转发,它的作用是保持原来的值属性不变。啥意思呢?通俗的讲就是,如果原来的值是左值,经std::forward处理后该值还是左值;如果原来的值是右值,经std::forward处理后它还是右值。

#include<iostream>template<typename T>
void print(T & t)
{std::cout<<"左值"<<std::endl;
}template<typename T>
void print(T && t)
{std::cout<<"右值"<<std::endl;
}template<typename T>
void testForward(T && v)
{print(v);print(std::forward<T>(v));print(std::move(v));
}int main()
{testForward(1);std::cout << "======================" << std::endl;int x = 1;testForward(x);    return 0;
}

左值
右值
右值

=========================
左值
左值
右值

从上面第一组的结果我们可以看到,传入的1虽然是右值,但经过函数传参之后它变成了左值(在内存中分配了空间);而第二行由于使用了std::forward函数,所以不会改变它的右值属性,因此会调用参数为右值引用的print模板函数;第三行,因为std::move会将传入的参数强制转成右值,所以结果一定是右值。

再来看看第二组结果。因为x变量是左值,所以第一行一定是左值;第二行使用forward处理,它依然会让其保持左值,所以第二也是左值;最后一行使用move函数,因此一定是右值。

5. std::forward内部实现

  /***  @brief  Forward an lvalue.*  @return The parameter cast to the specified type.**  This function is used to implement "perfect forwarding".*/template<typename _Tp>constexpr _Tp&&forward(typename std::remove_reference<_Tp>::type& __t) noexcept{ return static_cast<_Tp&&>(__t); }/***  @brief  Forward an rvalue.*  @return The parameter cast to the specified type.**  This function is used to implement "perfect forwarding".*/template<typename _Tp>constexpr _Tp&&forward(typename std::remove_reference<_Tp>::type&& __t) noexcept{static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"" substituting _Tp is an lvalue reference type");return static_cast<_Tp&&>(__t);}

从内部实现看std::forward(v)实际上就是做了一个static_cast的强转, 这个转换过程不是发生在函数内部,而是在函数外部就已经判断出来了_Tp是左值还是右值

std::forward必须配合T&&来使用。例如T&&接受左值int&时,T会被推断为int&,而T&&接受右值int&&时,T被推断为int。在std::forward中只是单纯的返回T&&。那么依据外层是左值时,T为int&,那么T&&即int& &&仍为int&,当外层函数参数为右值,T&&为int&&,这样就保证了传进来是左值则还是左值,是右值还是右值。

6. 常见的完美转发应用场景

用完美转发实现委托构造函数
委托构造函数允许一个构造函数调用同一个类的其他构造函数,从而避免代码重复。通过使用完美转发,我们可以更高效地在构造函数间传递参数。

class MyString {
public:template <typename... Args>MyString(Args&&... args) : _data(std::forward<Args>(args)...) {}
private:std::string _data;
};int main() 
{MyString s1("Hello, world!"); // 调用 std::string 的构造函数MyString s2(s1); // 调用 std::string 的拷贝构造函数MyString s3(std::move(s2)); // 调用 std::string 的移动构造函数
}

用完美转发实现可变参数模板函数
可变参数模板函数可以接受任意数量和类型的参数,通过使用完美转发,我们可以实现一个通用的元组或 bind 函数。

template <typename Func, typename... Args>
auto bind_and_call(Func&& func, Args&&... args) -> decltype(func(std::forward<Args>(args)...)) 
{return func(std::forward<Args>(args)...);
}
int sum(int a, int b, int c) 
{return a + b + c;
}int main() 
{int result = bind_and_call(sum, 1, 2, 3); // 完美转发参数给 sum 函数
}

用完美转发实现智能指针
智能指针是一种自动管理内存生命周期的对象,它可以确保在离开作用域时自动释放内存。通过使用完美转发,我们可以在智能指针的构造函数和 make 函数中避免不必要的拷贝操作

template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) 
{return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}class MyClass {
public:MyClass(int x, double y) : _x(x), _y(y) {}
private:int _x;double _y;
};int main() 
{auto ptr = make_unique<MyClass>(42, 3.14); // 完美转发参数给 MyClass 的构造函数
}

http://www.ppmy.cn/news/404071.html

相关文章

Python语法基础01(列表,元组,字典)

Python基础语法 变量的命名与使用 变量名只能包含字母、数字和下划线&#xff0c;只能以字母和下划线为开头不能包含空格不能使用python保留字 列表&#xff0c;元组&#xff0c;字典 列表 定义列表(元素之间可以没有任何关系)&#xff1a;[] 例如 fruits["apple&qu…

游泳可以戴的耳机有哪些?四款专业的游泳耳机推荐

现在人们都开始热衷于运动健身&#xff0c;运动时大多会听音乐&#xff0c;市面上的运动耳机层出不穷&#xff0c;多数都是蓝牙耳机&#xff0c;但是有一些运动不太适合。 例如游泳&#xff0c;其他运动都可以将手机放在附近&#xff0c;但是游泳就不行了。所以游泳时可以听歌的…

【Git原理与使用】-- 初步认识

目录 Git版本控制器的引入 版本控制器 Git安装&#xff08;已安装可以跳过&#xff09; Linux-centos Linux-ubuntu Git基本操作 创建Git本地仓库 配置 Git 认识工作区、暂存区、版本库 工作区、版本库 stage暂存区 工作区内容使用Git管理 Git版本控制器的引入 #&…

算法题模板

​ 文章目录 面试Tips算法题模板注意点&#xff1a;Master公式对数器 & 比较器一些知识Map.entrySet链表、二叉树SetMap 常见排序插入排序归并排序快速排序堆排序堆的应用 数组习题二分法查找二叉树二叉树遍历&#xff08;递归方法&#xff09;二叉树遍历&#xff08;非递归…

左程云算法题一些leetcode练习题总结

​ 面试Tips 业务为主技巧为主笔试55%45%笔试需要淘汰率&#xff0c;更考察经验&#xff0c;笔试卷面分过 35/100面试30%70%技巧为主反而更考察基础&#xff08;比如窗口最大值最小值、单调栈、动态规划斜率优化&#xff09; 算法题模板 注意点&#xff1a; 子串、子数组必…

左神bilibili算法笔记-新手班

复杂度和简单算法 认识时间复杂度 常数时间的操作 一个操作如果和样本的数据量没有关系&#xff0c;每次都是固定时间内完成的操作&#xff0c;叫做常数操作。 时间复杂度为一个算法流程中&#xff0c;常数操作数量的一个指标。常用0(读作big 0)来表示。具体 来说&#xff…

全网最全 数据结构 代码

原文 本文代码实现基本按照《数据结构》课本目录顺序&#xff0c;外加大量的复杂算法实现&#xff0c;一篇文章足够。能换你一个收藏了吧&#xff1f; 当然如果落下什么了欢迎大家评论指出 目录 顺序存储线性表实现 单链表不带头标准c语言实现 单链表不带头压缩c语言实现 约瑟夫…

手游开发者们,佛系青蛙会死吗?

点击上方“CSDN”&#xff0c;选择“置顶公众号” 关键时刻&#xff0c;第一时间送达&#xff01; 近段时间&#xff0c;朋友圈打招呼的方式&#xff0c;不是“你养蛙了吗&#xff1f;”&#xff0c;就是“你的蛙儿子去哪儿了&#xff1f;”有时&#xff0c;顺便配上一张蛙儿子…