本文为第八部分,目录请参阅概述部分:http://www.cnblogs.com/harrywong/p/cpp-rvalue-references-explained-introduction.html

Perfect Forwarding(完美转发):解决方案

对右值引用的两个规则中的第一个也同样影响了旧式的左值引用。回想在pre-11 C++时,对一个引用取引用是是不被允许的,比如A& &会导致一个编译器错误。相比之下,在C++11中,引入了如下所示的引用折叠规则reference collapsing rules):

  • A& &变成A&
  • A& &&变成A&
  • A&& &变成A&
  • A&& &&变成A&&

第二条,有一个对应于函数模板中模板参数的特殊的演绎规则,当其模板参数为右值引用类型的时候:

template<typename T>
void foo(T&&);

下面,这些规则会生效:

  1. 当foo被一个类型为A的左值调用时,T会被转化为A&,因此根据上面的引用折叠规则,这个参数类型实际上会变成A&。
  2. 当foo被一个类型为A的右值调用是,T会被转化成A,因此这个参数类型实际上会变成A&&。

根据这些规则,我们现在可以使用右值引用来解决如前所述的完美转发的问题了。这就是解决方案长的样子:

template<typename T, typename Arg>
shared_ptr<T> factory(Arg&& arg)
{
return shared_ptr<T>(new T(std::forward<Arg>(arg)));
}

std::forward的定义如下所示:

template<class S>
S&& forward(typename remove_reference<S>::type& a) noexcept
{
return static_cast<S&&>(a);
}

暂时还不用关注noexcept关键字。它就是让编译器知道,为了一些特定的优化目的,这些函数永远不会抛出异常。我们将会在第九部分探讨它。为了看清上面的代码是如何实现完美转发的,我们需要分别讨论当factory函数被左值和右值调用时发生了什么。给出A和x的类型。首先假设factory<A>被一个类型为x的左值所调用:

X x;
factory<A>(x);

然后,根据上面所讲的特殊的模板参数演绎规则,factory的模板类型Arg为转化为X&。因此,编译器将会实例化如下的factory和std::forward:

shared_ptr<A> factory(X& && arg)
{
return shared_ptr<A>(new A(std::forward<X&>(arg)));
} X& && forward(remove_reference<X&>::type& a) noexcept
{
return static_cast<X& &&>(a);
}

这对于左值而言的确是完美转发:factory函数的arg参数通过两层被间接地传递到了A的构造器中去,均是通过旧式的左值引用。

接下来,假设factory<A>被一个类型为x的右值所调用:

X foo();
factory<A>(foo());

那么,同样的根据特殊的模板参数演绎规则,factory的模板类型会被转化为X。因此现在编译器会产生如下函数模板实例:

shared_ptr<A> factory(X&& arg)
{
return shared_ptr<A>(new A(std::forward<X>(arg)));
} X&& forward(X& a) noexcept
{
return static_cast<X&&>(a);
}

这也确实是对右值的完美转发:factory函数的参数通过两层被间接地传递到了A的构造器中,都是通过引用。进一步来说,A的构造器看待它的参数就是一个被声明为右值引用的表达式,并且还没有名字。根据「没有名字规则」,它就是一个右值。因此,A的构造器被一个右值调用。这就说明了这次转发保留了move语义,就像factory的包装并不存在一样。

在这个上下文中,保留move语义是std::forward唯一的目的,这看上去可能并没什么价值。即使不使用std::forward,一切也会运作正常,除了A的构造器将总是会将他的参数看做一个有名字的,也就是左值。换另一种说法也就是std::forward的目的就是将左值还是右值的信息传递出去。

如果你想再深入一些,问你如下问题:为什么再std::forward的定义中remove_reference是需要的呢?答案便是:它根本并不需要。如果你在std::forward的定义中,使用S&来替换remove_reference<S>::type&,你依然可以发现完美转发依然运作正常。然后它运作正常是建立在我们明确的标志出Arg作为std::forward的函数模板参数而言的。在std::forward的定义中,使用remove_reference的目的便是强制我们如此去做。

值得庆祝,我们基本上已经完成了。就只剩下需要看一下std::move的实现了。需要记住,std::move的目的是为了将它的参数通过通过引用传递出去,并且把它绑定看起来像一个右值。下面便是实现:

template<class T>
typename remove_reference<T>::type&&
std::move(T&& a) noexcept
{
typedef typename remove_reference<T>::type&& RvalRef;
return static_cast<RvalRef>(a);
}

假设我们使用一个类型为x的左值调用它:

X x;
std::move(x);

根据新的模板演绎规则,模板参数T将会变成x&。因此,编译器最后实例化的是:

typename remove_reference<X&>::type&&
std::move(X& && a) noexcept
{
typedef typename remove_reference<X&>::type&& RvalRef;
return static_cast<RvalRef>(a);
}

然后评评估完remove_reference并且应用了新的引用折叠规则,这就变成:

X&& std::move(X& a) noexcept
{
return static_cast<X&&>(a);
}

它做了如下工作:我们的左值x绑定到了左值引用,也就是函数的参数类型。并且std::move函数将它直接传递,把它变成了一个无名的右值引用。

我把std::move在被右值调用时依然运作良好的证明留给你。但是你也许想要跳过这个:为什么有人想要用右值来调用std::move,当它唯一的目的就是将东西变成右值?并且,你也许现在还会注意到,除了

std::move(x);

我们还可以写

static_cast<X&&>(x);

然而,std::move是被强烈推荐的,因为它更具有表达性。

翻译「C++ Rvalue References Explained」C++右值引用详解 Part8:Perfect Forwarding(完美转发):解决方案的更多相关文章

  1. 翻译「C++ Rvalue References Explained」C++右值引用详解 Part1:概述

    本文系对「C++ Rvalue References Explained」 该文的翻译,原文作者:Thomas Becker. 该文较详细的解释了C++11右值引用的作用和出现的意义,也同时被Scot ...

  2. 翻译「C++ Rvalue References Explained」C++右值引用详解 Part3:右值引用

    本文为第三部分,目录请参阅概述部分:http://www.cnblogs.com/harrywong/p/4220233.html. 右值引用 如果x是任意类型,那么x&&则被称作一个 ...

  3. 翻译「C++ Rvalue References Explained」C++右值引用详解 Part6:Move语义和编译器优化

    本文为第六部分,目录请参阅概述部分:http://www.cnblogs.com/harrywong/p/cpp-rvalue-references-explained-introduction.ht ...

  4. 翻译「C++ Rvalue References Explained」C++右值引用详解 Part5:右值引用就是右值吗?

    本文为第五部分,目录请参阅概述部分:http://www.cnblogs.com/harrywong/p/cpp-rvalue-references-explained-introduction.ht ...

  5. 翻译「C++ Rvalue References Explained」C++右值引用详解 Part4:强制Move语义

    本文为第四部分,目录请参阅概述部分:http://www.cnblogs.com/harrywong/p/4220233.html. 强制Move语义 众所周知,正如C++标准的第一修正案所陈述:“委 ...

  6. 「翻译」Unity中的AssetBundle详解(二)

    为AssetBundles准备资源 使用AssetBundles时,您可以随意将任何Asset分配给所需的任何Bundle.但是,在设置Bundles时,需要考虑一些策略.这些分组策略可以使用到任何你 ...

  7. 「翻译」Unity中的AssetBundle详解(一)

    AssetBundles AssetBundle是一个存档文件,其中包含平台在运行时加载的特定资产(模型,纹理,预制,音频剪辑,甚至整个场景).AssetBundles可以表示彼此之间的依赖关系;例如 ...

  8. C++11标准之右值引用(rvalue reference)

    1.右值引用引入的背景 临时对象的产生和拷贝所带来的效率折损,一直是C++所为人诟病的问题.但是C++标准允许编译器对于临时对象的产生具有完全的自由度,从而发展出了Copy Elision.RVO(包 ...

  9. c++11-17 模板核心知识(十)—— 区分万能引用(universal references)和右值引用

    引子 如何区分 模板参数 const disqualify universal reference auto声明 引子 T&&在代码里并不总是右值引用: void f(Widget&a ...

随机推荐

  1. php缓存数组到文件

    php缓存数组到文件 static function getIDs($kemuid) { $cachefile="cache/" . $kemuid . ".cache& ...

  2. rpm包安装时发现缺少其他依赖

    多年来一直困扰我的问题,就是当我们下载了一个rpm包来安装的时候发现缺少依赖.以前的做法是网上挨个去搜索依赖的rpm,然后依次安装. # rpm -ivh google-chrome-stable_c ...

  3. Android 启动模拟器是出现“Failed to allocate memory: 8”错误提示的原因及解决办法

    某天,Android 启动模拟器是出现“Failed to allocate memory: 8”错误,模拟器无法启动,如下图: 原因:设置了不正确AVD显示屏模式,4.0版默认的模式为WVGA800 ...

  4. SOA_环境安装系列5_Oracle ADF安装SOA Extensions(案例)

    2015-01-02 Created By BaoXinjian

  5. Grid_Oracle Grid Infrastructure概念介绍(概念)

    2014-03-09 Created By BaoXinjian Thanks and Regards

  6. 2016 Multi-University Training Contest 5 ATM Mechine

    ATM Mechine 本文转自:http://blog.csdn.net/queuelovestack/article/details/52096337 题意: 这题的意思还是比较费解的 Alice ...

  7. 51nod 1120 机器人走方格 V3 卡特兰数 lucas定理

    N * N的方格,从左上到右下画一条线.一个机器人从左上走到右下,只能向右或向下走.并要求只能在这条线的上面或下面走,不能穿越这条线,有多少种不同的走法?由于方法数量可能很大,只需要输出Mod 100 ...

  8. 用happen-before规则重新审视DCL(转)

    编写Java多线程程序一直以来都是一件十分困难的事,多线程程序的bug很难测试,DCL(Double Check Lock)就是一个典型,因此对多线程安全的理论分析就显得十分重要,当然这决不是说对多线 ...

  9. jmeter测试某个QPS下的响应时间-设置QPS限制

    本次性能测试的需求中提到测试的目的是“了解博客的首页在负载达到20 QPS时的响应时间”,因此需要控制向博客首页发送请求的负载为20QPS. 一种可行的方法是逐步调整测试计划中的线程计算的数量以及为取 ...

  10. cookie和会话状态的工作原理

    一:存在两种类型的cookie: 1>会话cookie (session cookie)        不设置过期时间,则表示这个cookie生命周期为浏览器会话期间,只要关闭浏览器窗口,   ...