IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++右值语义的基石——完美转发 -> 正文阅读

[C++知识库]C++右值语义的基石——完美转发


个人小站版本链接

什么是完美转发?

熟悉现代C++语法的都应该清楚,C++把变量分为左值和右值,为了实现对资源的转移而不是拷贝,右值和对应的移动构造函数应运而生,但我们发现,很多时候我们并不能把左值和右值精确的传递给对应版本的函数进行处理,比如下面一个简单的代码,你会发现即使我们把函数的参数类型设置为右值引用,但当拿它去调用对应的构造函数时,它给出的竟然是拷贝构造!故这个转发还不够完美!

#include<iostream>
using namespace std;
class test{
public:
    test() = default;
    test(test&& p){
        cout<<"move construct call"<<endl;
    }
    test(const test& p){
        cout<<"copy construct call"<<endl;
    }
};
void test_fun(test&& p){
    test q(p);
    return;
}

int main() {
    test_fun(test());
    return 0;
}

为什么会出现这种情况呢?

因为无论传入的形参是左值还是右值,对于函数内部来说,形参既有名称又能寻址,因此它都被认为是左值

如何实现完美转发?

实现完美转发很简单,我们在现代C++中只需要 forward<T> 这个模板函数即可完成,其实际原理就是利用的 C++11 模板中提供的折叠引用的语法,最终达到的效果就是,把参数的类型强制转换为它该有的类型,是左值就转为左值,是右值就转为右值,从而实现该调用哪个版本的函数就调用哪个版本的函数,不再只被认定为右值了!

先前的代码可以如此实现完美转发:

#include<iostream>
using namespace std;
class test{
public:
    test() = default;
    test(test&& p){
        cout<<"move construct call"<<endl;
    }
    test(const test& p){
        cout<<"copy construct call"<<endl;
    }
};
void test_fun(test&& p){
    test q(forward<test>(p)); //修改的地方
    return;
}

int main() {
    test_fun(test());
    return 0;
}

任何模板库都离不开完美转发

其实现在只要是C++的模板库,没有哪个是不用完美转发的,同时完美转发的问题也是产生自模板,而 forward 函数的实现其实也不是什么难事,实际就是利用 C++11 对模板提供的万能折叠语义:

  • 当实参为左值或者左值引用(A&)时,函数模板中 T&& 将转变为 A&(A& && = A&);
  • 当实参为右值或者右值引用(A&&)时,函数模板中 T&& 将转变为 A&&(A&& && = A&&)。

以下为一个简单的利用完美转发设计的创建工厂:

#include<iostream>
#include <memory>

using namespace std;
class test{
public:
    test() = default;
    test(int&& arg):m_iData(arg){
        cout<<"move construct call"<<endl;
    }
    test(const int& arg):m_iData(arg){
        cout<<"copy construct call"<<endl;
    }
private:
    int m_iData;
};

template<typename T,typename Arg>
//不直接用T&&的原因在于,如果只使用一个模板参数会导致factory参数无法获得万能引用的效果
shared_ptr<T> factory(Arg&& arg){
    return shared_ptr<T>(new T(forward<Arg>(arg)));//使用完美转发调用正确的构造函数
}

int main() {
    int val = 5;
    auto p1 = factory<test>(val);
    auto p2 = factory<test>(5);
    return 0;
}

std::forward的实现原理

gcc 的源代码实现如下:

template<typename _Tp>
constexpr _Tp &&
forward(typename std::remove_reference<_Tp>::type &__t) noexcept { return static_cast<_Tp &&>(__t); }

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);
}

我们发现,源代码中实现了两个模板特化,_Tp&_Tp&& 但最终都是通过 static_cast + 折叠引用的特性来实现强制转化的。也就是简单的强转而已!

收获

在设计模板库的时候,如果需要根据左值右值语义比较清晰的实现转发,一定要用forward,否则参数只会被当作左值!

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-02-05 21:32:58  更:2022-02-05 21:33:31 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:11:57-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码