C++ 11右值引用与移动语义 -----下


0、完美转发(定义)

   所谓转发,就是通过一个函数将参数继续转交给另一个函数进行处理,原参数可能是右值,可能是左值,如果还能继续保持参数的原有特征,那么它就是完美的。

1、完美转发(存在的问题)

   右值引用还可以被用来解决完美转发的问题,考虑下面的代码,很明显代码的意图是将Arg 参数通过 factory 函数转发到 T 的构造函数中。理想情况下,就参数Arg而言,就像factory 函数不存在,直接调用客户代码的构造函数一样,即完美转发。不幸的是,代码在这种状况下会失败:拷贝构造函数中采取值传递的方式,会导致无限循环。

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

   通常情况下,我们采用 boost::bind 来解决上面的问题。

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

   但是上面的解决办法也不是完美的,现在的问题是 factory 函数不能传入右值作为参数。

factory<X>(hoo()); //如果函数 hoo() 以值传递的方式返回
factory<X>(41); // 错误

常量引用const & 定义表面上可以 解决上面的问题

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

   但是使用常引用存在两个问题:首先,如果factory 不止一个参数,有多个参数,需要提供所有相应的重载拷贝构造函数,可扩展性差;其次,这种情形下的转发不能成为完美转发,因为并没有利用移动语义,因为T 中的参数是以值传递的方式。
   以上的两个问题可以被右值引用解决,以实现真正意义上的完美转发,在介绍右值引用如何解决这个问题前,需要先介绍下面的内容。

2、完美转发(解决办法)

   首先介绍C++11 中的新特性:允许对引用取引用。在之前的版本中,A& & 会编译出错,但是在 C++11 中,引用重叠解释为下面的含义。

A& & becomes A&
A& && becomes A&
A&& & becomes A&
A&& && becomes A&&

   其次,C++ 11 对于函数模板,有一个特别参数规则:当其参数声明为右值引用时,(1)当传入左值的时候,T 调用的是 A& 构造函数,因为 上面的引用重叠规则;(2)当传入右值的时候,T 调用的是 A&& 构造函数。

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

有了上面的新特性之后,

template<typename T, typename Arg> 
shared_ptr<T> factory(Arg&& arg)
{ 
  return shared_ptr<T>(new T(std::forward<Arg>(arg)));
} 
//其中,forward 定义如下
template<class S>
S&& forward(typename remove_reference<S>::type& a) noexcept
//不用关心关键字 noexcept,这个关键字是为了告诉编译器这个函数不抛出异常
{
  return static_cast<S&&>(a);
} 

结合引用重叠规则,分析, 以 X 类型 为例:

  • 如果传入的参数是 X 左值类型的,则 arg 被推导为 X&,然后传递给std::forward 的模板参数,X& &&被解释为 X &,实现了完美转发;
X x;
factory<A>(x);
shared_ptr<A> factory(X& && arg)
{ 
  return shared_ptr<A>(new A(std::forward<X&>(arg)));
} 

X& && forward(remove_reference<X&>::type& arg) noexcept
{
  return static_cast<X& &&>(arg);
}

利用引用重叠规则,可解释为:

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

X& std::forward(X& a) 
{
  return static_cast<X&>(a);
} 
  • 如果传入的参数是 X 右值类型的,则 arg 被推导为 X&&,然后传递给std::forward 的模板参数,X&& &&被解释为 X &&,实现了完美转发;
//X fun();
factory<A>(fun());
shared_ptr<A> factory(X&& && arg)
{ 
  return shared_ptr<A>(new A(std::forward<X&&>(arg)));
} 

X&& && forward(remove_reference<X&&>::type& arg) noexcept
{
  return static_cast<X&& &&>(arg);
}

利用重叠引用规则,简化为:

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

X  && forward(remove_reference<X&&>::type& arg) noexcept
{
  return static_cast<X  &&>(arg);
}
  • 如果传入的参数是 X & 类型的,则 arg 被推导为 X &,然后传递给std::forward 的模板参数,X& &&被解释为 X &,实现了完美转发;
shared_ptr<A> factory(X & && arg)
{ 
  return shared_ptr<A>(new T(std::forward<X &>(arg)));
} 
X &  && forward(typename remove_reference<X &>::type& a) noexcept
{
  return static_cast<X & &&>(a);
} 

利用引用重叠规则,可解释为:

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

X&  std::forward(X& a) 
{
  return static_cast<X&>(a);
} 
  • 如果传入的参数是 const X & 类型的,则 arg 被推导为 const X &,然后传递给std::forward 的模板参数,const X& &&被解释为 const X &,实现了完美转发;

  • 如果传入的参数是 const X 类型的,则 arg 被推导为 const X &,然后传递给std::forward 的模板参数,const X& &&被解释为 const X &,实现了完美转发;

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

假定我们调用move() , 传入左值作为参数,则代码会被解释为

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

简化为

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

   从上面的代码可以看出,move() 函数中,当传入一个左值x的时候,左值将绑定为右值引用,然后将其转化为右值引用。

2、右值引用与异常

   如果在代码中使用右值引用,应该注意下面两个方面:

  • 确保重载的右值引用拷贝构造函数或者赋值运算符 编写正确,这个很简单,因为移动语义通常就是交换两个对象的指针和它们控制的资源而已,确保这部分不出错即可;
  • 使用关键字 noexcept 来告诉编译器在重载的右值引用拷贝构造函数或者赋值运算符中不要抛出异常;

   正确使用右值引用需要时刻记住下面三点:

  1. 重载函数,像下面的定义一样,这样可以在编译期间,确定调用的是哪个函数。当然其中比较关键的是当使用移动语义的时候对于 拷贝构造函数和赋值运算符的重载。编写重载函数的时候一定要注意异常的处理,确保程序编写正确的前提下,尽量使用关键字 noexcept
void foo(X& x); // 左值引用重载
void foo(X&& x); // 右值引用重载
  1. std::move() 将传入的参数转化为 右值

  2. std::forward() 可以实现真正的完美转发。

分类 C++
  

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值