现代C++技术研究(8)---完美转发

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

我们知道,常量左值引用既可以绑定左值,又可以绑定右值,这是C++98就存在的特性。对于如下模板函数,使用了常量左值引用,并在函数中调用了std::vector的emplace_back函数:

#include <iostream>
#include <vector>

struct MyStruct {
    MyStruct() { std::cout << "construct is called\n"; }
    ~MyStruct() { std::cout << "destruct is called\n"; }
    MyStruct(const MyStruct&) {std::cout << "copy construct is called\n";}
    MyStruct(MyStruct&&) { std::cout << "move construct is called\n"; }
};

std::vector<MyStruct> g_v;

template<typename T>
void MyFunc(const T& param) noexcept
{
    std::cout << "MyFunc is called\n";
    g_v.emplace_back(param);
}

int main()
{
    g_v.reserve(100);
    MyStruct a;
    MyFunc(a);
    MyFunc(MyStruct());
    return 0;
}

运行的效率不怎么样,因为函数入参是常量左值引用,在调用emplace_back函数的内部发生了拷贝动作,可以看看运行结果:

construct is called
MyFunc is called
copy construct is called
construct is called
MyFunc is called
copy construct is called
destruct is called
destruct is called
destruct is called
destruct is called

为了解决这个冗余拷贝的问题,我们首先想到了前面讨论过的Universal Reference:

#include <iostream>
#include <vector>

struct MyStruct {
    MyStruct() { std::cout << "construct is called\n"; }
    ~MyStruct() { std::cout << "destruct is called\n"; }
    MyStruct(const MyStruct&) {std::cout << "copy construct is called\n";}
    MyStruct(MyStruct&&) { std::cout << "move construct is called\n"; }
};

std::vector<MyStruct> g_v;

template<typename T>
void MyFunc(T&& param) noexcept
{
    std::cout << "MyFunc is called\n";
    g_v.emplace_back(param);
}

int main()
{
    g_v.reserve(100);
    MyStruct a;
    MyFunc(a);
    MyFunc(MyStruct());
    return 0;
}

Universal Reference可以把右值转成右值引用,左值转成左值引用。测试结果出乎预料:

construct is called
MyFunc is called
copy construct is called
construct is called
MyFunc is called
copy construct is called
destruct is called
destruct is called
destruct is called
destruct is called

和前面没有区别,查看emplace_back的定义:

template< class... Args >
void emplace_back( Args&&... args );

是一个Universal Reference,左值推导出左值,因此不能触发移动构造,这个可以理解,但是右值引用最后也不能触发移动构造,这个就有点说不过去了。据说是这个原因:虽然是右值引用,但是因为是函数的命名参数,所以编译器仍然把这个作为左值来看待,因此不触发移动构造(编译器比较保守),只有调用std::move或者std::forward或者使用static_cast显示类型强转为右值引用,编译器才会触发移动构造,那么我们先加std::move试试看:

#include <iostream>
#include <vector>

struct MyStruct {
    MyStruct() { std::cout << "construct is called\n"; }
    ~MyStruct() { std::cout << "destruct is called\n"; }
    MyStruct(const MyStruct&) {std::cout << "copy construct is called\n";}
    MyStruct(MyStruct&&) { std::cout << "move construct is called\n"; }
};

std::vector<MyStruct> g_v;

template<typename T>
void MyFunc(T&& param) noexcept
{
    std::cout << "MyFunc is called\n";
    g_v.emplace_back(std::move(param));
}

int main()
{
    g_v.reserve(100);
    MyStruct a;
    MyFunc(a);
    MyFunc(MyStruct());
    return 0;
}

测试结果令人满意,两次拷贝构造调用都变成了移动构造调用:

construct is called
MyFunc is called
move construct is called
construct is called
MyFunc is called
move construct is called
destruct is called
destruct is called
destruct is called
destruct is called

如果改成std::forward:

#include <iostream>
#include <vector>

struct MyStruct {
    MyStruct() { std::cout << "construct is called\n"; }
    ~MyStruct() { std::cout << "destruct is called\n"; }
    MyStruct(const MyStruct&) {std::cout << "copy construct is called\n";}
    MyStruct(MyStruct&&) { std::cout << "move construct is called\n"; }
};

std::vector<MyStruct> g_v;

template<typename T>
void MyFunc(T&& param) noexcept
{
    std::cout << "MyFunc is called\n";
    g_v.emplace_back(std::forward<T>(param));
}

int main()
{
    g_v.reserve(100);
    MyStruct a;
    MyFunc(a);
    MyFunc(MyStruct());
    return 0;
}

测试结果是传入右值对象那个调用触发了移动构造:

construct is called
MyFunc is called
copy construct is called
construct is called
MyFunc is called
move construct is called
destruct is called
destruct is called
destruct is called
destruct is called

这个其实也就是std::forward和std::move的区别,std::move一律强转为右值引用,而std::forward只是转发,把右值或右值引用强转为右值引用,把左值和左值引用强转为左值引用。可以看一下gcc的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>
    _GLIBCXX_NODISCARD
    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>
    _GLIBCXX_NODISCARD
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
    {
      static_assert(!std::is_lvalue_reference<_Tp>::value,
	  "std::forward must not be used to convert an rvalue to an lvalue");
      return static_cast<_Tp&&>(__t);
    }

两个局部特化的模板,一个用于强转为左值引用,一个用于强转为右值引用。其实相当于static_cast<T&&>(param);实现成两个特化版本,主要是为了防止使用者不当使用场景下,通过static_assert断言,引起编译失败。当然std::forward目的就是为了转发,因此不应该在所有场景下都去做右值类型转换。

小结一下:

1)仅仅通过Universal Reference的类型推导获得的结果类型,还不足以实现完美转发,还要和std::forward相结合才行,并且std::forward仅适合在模板函数中使用;

2)std::move会对所有类型强制转换成右值引用,而std::forward仅对右值或者右值引用强制转换成右值引用,对左值或左值引用则会强转为左值引用。

3)在模板函数中,通过Universal Reference的隐式类型推导得到的右值引用,编译器不会触发移动构造,必须要通过std::move或者std::forward或者static_cast显示类型转换为右值引用,编译器才会触发移动构造。

参考资料:

《深入理解C++11》

《Effective Modern C++》文章来源地址https://www.toymoban.com/news/detail-469912.html

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

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

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

相关文章

  • C++左值右值完美转发转移

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

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

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

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

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

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

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

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

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

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

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

    2024年01月25日
    浏览(37)
  • C++右值引用(左值表达式、右值表达式)(移动语义、完美转发(右值引用+std::forward))(有问题悬而未决)

    在 C++ 中,表达式可以分为左值表达式和右值表达式。左值表达式指的是可以出现在赋值语句左边的表达式,例如变量、数组元素、结构体成员等;右值表达式指的是不能出现在赋值语句左边的表达式,例如常量、临时对象、函数返回值等。 右值是指将要被销毁的临时对象或

    2024年02月04日
    浏览(42)
  • 【现代密码学基础】详解完美安全与不可区分安全

    目录 一. 介绍 二. 不可区分性试验 三. 不可区分性与完美安全 四. 例题 五. 小结 敌手完美不可区分,英文写做perfect adversarial indistinguishability,其中adversarial经常被省略不写,在密码学的论文中经常被简称为IND安全。 完美不可区分与香农的完美安全是类似的。该定义来源于一

    2024年01月21日
    浏览(53)
  • 【探索AI潜能,连结现代通讯】相隔万里,我们与AI一同赏月。

    近年来,AI得到了迅猛的发展,尤其是大模型的出现受到了广泛的关注和讨论。 ChatGPT、文心一言 等纷纷登场,可谓是百家争鸣。 而AI大模型所延申出的子项目如 AI绘画、AI写作 等,在各自的领域展示出了惊人的潜力。 最圆的月亮在 中秋 🌔,最好的团聚在 家里 🏠。然而许

    2024年02月08日
    浏览(30)
  • Spring Boot与Kubernetes:现代云部署的完美组合

    🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页 ——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文并茂🦖生动形象🐅简单易学!欢迎大家来踩踩~🌺 🌊 《IDEA开发秘籍专栏》 🐾 学会IDEA常用操作,工作效率翻倍~💐 🌊 《100天精通Golang(基础

    2024年02月09日
    浏览(38)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包