C++之std::forward(完美转发)

这篇具有很好参考价值的文章主要介绍了C++之std::forward(完美转发)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

相关系列文章

C++之std::is_object

C++之std::decay

C++模板函数重载规则细说

C++之std::declval

C++之std::move(移动语义)

C++之std::forward(完美转发)

C++之std::enable_if

C++之std::is_pod(平凡的数据)

目录

1.简介

2.完美转发原理

3.完美转发失败的情形

4.实例讲解


1.简介

        std::forward是C++11引入的函数模板,它的作用是实现函数参数的完美转发,通俗的讲就是根据传入的参数,决定将参数以左值引用还是右值引用的方式进行转发。

        std::forward原型:

//左值版本
template <class _Ty>
_NODISCARD constexpr _Ty&& forward(
    remove_reference_t<_Ty>& _Arg) noexcept { // forward an lvalue as either an lvalue or an rvalue
    return static_cast<_Ty&&>(_Arg);
}

//右值版本
template <class _Ty>
_NODISCARD constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept { // forward an rvalue as an rvalue
    static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");
    return static_cast<_Ty&&>(_Arg);
}

2.完美转发原理

        在C++中,存在左值(lvalue)和右值(rvalue)的概念。关于左值和右值的概念,自己可以查询相关资料。简单来说,左值是指可以取地址的、具有持久性的对象,而右值是指不能取地址的、临时生成的对象。传统上,当一个左值传递给一个函数时,参数会以左值引用的方式进行传递;当一个右值传递给一个函数时,参数会以右值引用的方式进行传递。

        以VS2019的std::forward源码实现来讲解:

std::forward,#C++进阶,c++,开发语言

1. 当传递给func函数的实参类型为左值QObject时,T被推导为QObject&类别。然后forward会实例化为std::forward<QObject&>,并返回QObject&(左值引用,根据定义是个左值!)

2. 而当传递给func函数的实参类型为右值QObject时,T被推导为QObject。然后forward被实例化为std::forward<QObject>,并返回QObject&&(注意,匿名的右值引用是个右值!)

3. 可见,std::forward会根据传递给func函数实参(注意,不是形参)的左/右值类型进行转发。当传给func函数左值实参时,forward返回左值引用,并将该左值转发给doWork。而当传入func的实参为右值时,forward返回右值引用,并将该右值转发给doWork函数。

3.完美转发失败的情形

在std::forward中,失败的情况其实就是完美转发的目的没有达到,甚至根本无法实现

1、花括号的初始化方式调用

#include <iostream>
#include <array>

void test(const std::array<int,5> &param){}

template<typename T>
void func(T&& t)
{
    test(std::forward<T>(t));
}
int main()
{
    std::array<int, 5> arr = {0,1,2};
    func(arr);
   // func({0,1,2,3,4});
}

解决方案:先用auto声明一个局部变量,再将该局部变量传递给转发函数。

2、0或者NULL做为空指针传递

其实这个也是c++11后强调使用nullptr做为指针的空值的一个重要原因。之所以会这样,是因为0和NULL往往会被默认转成为int类型,这也是在早期的C编译器中经常遇到的一个编译现象,就是“XXX无法转成int”其实就是写错了,但默认就是往int上靠,这个没办法。一如下面:

void test( void *ptr){}

template<typename T>
void func(T&& t)
{
    test(std::forward<T>(t));
}
int main()
{
    func(0);
}

解决方案:传递nullptr,而非0或NULL

3、static const的应用(含constexpr)

void test(int i){}

class Data {
public:
    static const int d_ = 1;
};
//const  int Data::d_ ;

template<typename T>
void func(T&& t)
{
    test(std::forward<T>(t));
}
int main()
{
    func(Data::d_);
}

这段代码在VS中可以成功运行,但在g++中会报一个链接错误“ undefined reference to Data::d_ collect2: error: ld returned 1 exit status”。VS中对编译和链接相对来说还是宽松一些。
想通过连接只需要把注释的部分解开就可以了。
解决方案:在类外定义该成员变量。注意这声变量在声明时一般会先给初始值。因此定义时无需也不能再重复指定初始值

4、对重载和模板的函数名处理
这句话的意思是指在完美转发时如果参数是一个函数名称,那么如果这个函数名称的函数如果存在重载(或是模板)的话,在普通编程的直接调用情况下是没有问题,但是在完美转发时,不管是直接调用名称还是使用模板函数时会存在转发的错误。看下面的代码就理解了

void myfunc_test(void func(int)) {}

void myfunc(int a) {}
void myfunc(int a, int b) {}

template<typename T>
void dotest(T t){}
template<typename T>
void func(T&& t)
{
    myfunc_test(std::forward<T>(t));
}

int main()
{
   // func(myfunc);
   // func(dotest);
    myfunc_test(myfunc);
}

一般来说,传递函数做为函数参数时,用函数指针的情况很多,但也应该知道,也可以直接传递函数名称而不是指针的情况来操作。上面的代码就是这样,就会出现本节的问题。但是如果直接调用函数myfunc_test而非完美转发,则没有问题。同样,函数模板也是如此,因为函数模板毕竟不是一个实例,而是一组类似实例的泛型。
想要解决这个问题也很简单,依照着普通编译成功的方式,采用指针的方式直接指明调用的函数即可,模板也同样如此。
5、位字段

位域是由机器字的若干任意部分组成的(如32位int的第3至5个比特),但这样的实体是无法直接取地址的。而fwd的形参是个引用,本质上就是指针,所以也没有办法创建指向任意比特的指针。

解决方案:制作位域值的副本,并以该副本来调用转发函数。

struct xxxxx
{
    std::uint32_t v: 4,
                  I : 4,
                  D : 6,
                  E : 2,
                  t : 16;
    //...
};

void test(std::uint16_t  v) {}

template<typename T>
void func(T&& t)
{
    test(std::forward<T>(t));
}
int main()
{
   xxxxx ip = {};
    test(ip.t);  //调用void func(int)
    //func(ip.t); //error,func形参是引用,由于位域是比特位组成。无法创建比特位的引用!

    //解决方案:创建位域的副本,并传给func
    auto length = static_cast<std::uint16_t>(ip.t);
    func(length);
}

4.实例讲解

实例:文章来源地址https://www.toymoban.com/news/detail-848259.html

template <typename _Callable, typename... _Types>
auto invoke(_Callable&& obj, _Types... argv)
{
	return _Invoke<_Callable, _Types...>::_Call(
		std::forward<_Callable>(obj),
		std::forward<_Types>(argv)...);
}

到了这里,关于C++之std::forward(完美转发)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 任务队列C++实现-(完美转发)

    任务队列中可以 依次添加任务 ; 任务执行函数需要 接受外部传输的参数 ; 主动 调用Start开始执行任务 ; 任务队列,将需要执行的任务存储在队列中,存储的这个动作类似于 生产者 ; 当任务队列不为空时,会从队列中取出一个任务执行,当任务执行结束后再从队列取下

    2024年02月07日
    浏览(27)
  • C++——移动构造和完美转发

    右值引用是C++11的概念,与之对应的是左值引用。 当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存当中的位置)。 以上的概念是摘录自《C++ primer》。 但是这样的概念并不足以理解。 用一句简单的话描述左值和右值

    2024年02月12日
    浏览(23)
  • 现代C++技术研究(8)---完美转发

    我们知道,常量左值引用既可以绑定左值,又可以绑定右值,这是C++98就存在的特性。对于如下模板函数,使用了常量左值引用,并在函数中调用了std::vector的emplace_back函数: 运行的效率不怎么样,因为函数入参是常量左值引用,在调用emplace_back函数的内部发生了拷贝动作,

    2024年02月07日
    浏览(24)
  • C++左值右值完美转发转移

    英文含义: 左值(Lvalue) : Locator value ,意味着它指向一个具体的内存位置。 右值(Rvalue) : Read value ,指的是可以读取的数据,但不一定指向一个固定的内存位置。 定义 左值 :指的是一个持久的内存地址。左值可以出现在赋值操作的左侧或右侧。例如,变量、数组的元

    2024年03月10日
    浏览(44)
  • c++右值引用、移动语义、完美转发

    左值:一般指的是在内存中有对应的存储单元的值,最常见的就是程序中创建的变量 右值:和左值相反,一般指的是没有对应存储单元的值(寄存器中的立即数,中间结果等),例如一个常量,或者表达式计算的临时变量 左值引用:C++中采用 对变量进行引用,这种常规的引

    2024年02月05日
    浏览(48)
  • 【重学C++】04 | 说透C++右值引用、移动语义、完美转发(上)

    【重学C++】04 | 说透C++右值引用、移动语义、完美转发(上) 大家好,我是只讲技术干货的会玩code,今天是【重学C++】的第四讲,在前面《03 | 手撸C++智能指针实战教程》中,我们或多或少接触了右值引用和移动的一些用法。 右值引用是 C++11 标准中一个很重要的特性。第一

    2024年02月06日
    浏览(33)
  • 【C++学习】C++11——新特性 | 右值引用 | 完美转发

    🐱作者:一只大喵咪1201 🐱专栏:《C++学习》 🔥格言: 你只管努力,剩下的交给时间! C++的发展截至到目前为止,虽然版本有很多,但是C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一

    2024年02月06日
    浏览(26)
  • 【重学C++】05 | 说透右值引用、移动语义、完美转发(下)

    【重学C++】05 | 说透右值引用、移动语义、完美转发(下) 大家好,我是只讲技术干货的会玩code,今天是【重学C++】的第五讲,在第四讲《【重学C++】04 | 说透右值引用、移动语义、完美转发(上)》中,我们解释了右值和右值引用的相关概念,并介绍了C++的移动语义以及如

    2024年02月06日
    浏览(32)
  • 【C++干货铺】C++11新特性——右值引用、移动构造、完美转发

    ========================================================================= 个人主页点击直达:小白不是程序媛 C++系列专栏:C++干货铺 代码仓库:Gitee ========================================================================= 目录 左值与左值引用 右值与右值引用 左值引用和右值引用的比较  左值引用总结:

    2024年01月25日
    浏览(30)
  • 从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值

    目录 1. 列表初始化initializer_list 2. 前面提到的一些知识点 2.1 小语法 2.2 STL中的一些变化 3. 右值和右值引用 3.1 右值和右值引用概念 3.2 右值引用类型的左值属性 3.3 左值引用与右值引用比较 3.4 右值引用的使用场景 3.4.1 左值引用的功能和短板 3.4.2 移动构造 3.4.3 移动赋值

    2024年02月12日
    浏览(26)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包