注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

还东国的博客

行之苟有恒,久久自芬芳

 
 
 

日志

 
 

(转载)右值引用---c++0x新特性 ——续  

2013-04-17 16:52:36|  分类: C++(VC)编程 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

继续上一篇的转载,网易的BLOG不给力啊,字数总是限制,要不就是敏感字过滤不通过,这编程的水平,够厉害。

第三篇:你的下一步转移

这是关于C++中的高效值类型的系列文章中的第三篇。在上一篇中,我们介绍了C++0x的右值引用,描述了如何建立一个可转移类型,并示范了如何显式地利用可转移性。现在我们来看看转移优化的其它一些机会,开拓一些关于转移方面的新领域。

复活一个右值

在开始讨论进一步的优化之前,我们要先了解,一个匿名的右值引用是右值,而一个命名的右值引用则是左值。我把它写下来以便你记得更清楚:

切记:一个命名的右值引用是左值。

我承认这有点不合情理,但是请看以下例子:

  1. int g(X const&);  // logically non-mutating   
  2. int g(X&&);       // ditto, but moves from rvalues   
  3.    
  4. int f(X&& a)  
  5. {  
  6.     g(a);  
  7.     g(a);  
  8. }  

如果我们把 f 中的 a 视为右值,那么第一次对 g 的调用将会从 a 进行转移,第二次调用将会看到一个被修改过的 a。这不仅是反直觉的;而且它违反了一个保证,即调用 g 不会有可见的对任何东西的修改。所以,一个命名的右值引用是和其它引用一样的,只有匿名的右值引用才被特殊对待。为了给第二次的 g 调用一个机会进行转移,我们必须如下重写:

  1. #include <utility> // for std::move   
  2. int f(X&& a)  
  3. {  
  4.     g(a);  
  5.     g( std::move(a) );  
  6. }  

要记得 std::move 本身并不进行转移。它只是将其参数转换为匿名右值引用,这样接下来就可以进行转移。

二元操作符

转移语义对于优化二元操作符的使用尤其有用。考虑以下代码:

  1. class Matrix  
  2. {  
  3.      …  
  4.      std::vector<double> storage;  
  5. };  
  6.    
  7. Matrix operator+(Matrix const& left, Matrix const& right)  
  8. {  
  9.     Matrix result(left);  
  10.     result += right;   // delegates to +=   
  11.     return result;  
  12. }  
  13. Matrix a, b, c, d;  
  14. …  
  15. Matrix x = a + b + c + d;  

每一次调用 operator+ 时,都要执行 Matrix 的复制构造函数来创建 result。因此,即使RVO可以消除返回时对 result 的复制,上述表达式还要是进行三次 Matrix 的复制(每个 + 各一次),每次都要构造一个很大的 vector。复制省略可以让这些 result 矩阵中的一个与 x 成为同一个对象,但是另外两个还是要被销毁的,这是额外的开销。

我们可以写一个令这个表达式表现更好的 operator+,这种方法在C++03中也是可以的:

  1. // Guess that the first argument is more likely to be an rvalue   
  2. Matrix operator+(Matrix x, Matrix const& y)  
  3. {  
  4.     x += y;        // x was passed by value, so steal its vector   
  5.     Matrix temp;   // Compiler cannot RVO x, so   
  6.     swap(x, temp); // make a new Matrix and swap   
  7.     return temp;  
  8. }  
  9. Matrix x = a + b + c + d;  

一个可以尽可能消除复制的编译器按此实现可以做到近似最优的结果,只创建一个临时对象并将其内容直接转移给 x。但是,以下这种难看的写法可以很容易地让我们的优化失效:

  1. Matrix x = a + (b + (c + d));  

实际上,这比我们原来的实现更糟糕:现在右值总是出现在 + 号的右边而被显式地复制。左值却总是出现在左边,但由于它是传值的,所以隐式的复制无法被消除,这样我们得到了六次复制的代价。

不过,有了过右值引用,我们可以通过在原有实现上增加一些重载来进行可靠的优化工作:

  1. // The "usual implementation"   
  2. Matrix operator+(Matrix const& x, Matrix const& y)  
  3. { Matrix temp = x; temp += y; return temp; }  
  4.    
  5. // --- Handle rvalues ---   
  6.    
  7. Matrix operator+(Matrix&& temp, const Matrix& y)  
  8. { temp += y; return std::move(temp); }  
  9.    
  10. Matrix operator+(const Matrix& x, Matrix&& temp)  
  11. { temp += x; return std::move(temp); }  
  12.    
  13. Matrix operator+(Matrix&& temp, Matrix&& y)  
  14. { temp += y; return std::move(temp); }  

只可转移的类型

有些类型确实不应该被复制,但是以传值方式传递它们、从函数返回它们、把它们保存在容器中却又非常有意义。一个你可能很熟悉的例子就是 std::auto_ptr<T>:你可以调用它的复制构造函数,实际上但却并没有进行复制。相反...进行了转移!从方程式推理来讲,以复制语义从左值进行转移甚至比引用语义更为糟糕。如果从容器中复制一个值会改变原序列的话,那么对一个 auto_ptr 的容器进行排序有什么意义。

由于这些原因,原标准明确禁止将 auto_ptr 放入标准容器中,而且在C++0x中 auto_ptr 已被淘汰。取而代之的是一个不能复制只能转移的新型智能指针:

  1. template <class T>  
  2. struct unique_ptr  
  3. {  
  4.  private:  
  5.     unique_ptr(const unique_ptr& p);  
  6.     unique_ptr& operator=(const unique_ptr& p);  
  7.  public:  
  8.     unique_ptr(unique_ptr&& p)  
  9.       : ptr_(p.ptr_) { p.ptr_ = 0; }  
  10.    
  11.     unique_ptr& operator=(unique_ptr&& p)  
  12.     {  
  13.         delete ptr_; ptr_ = p.ptr_;  
  14.         p.ptr_ = 0;  
  15.         return *this;  
  16.     }  
  17. private:   
  18.     T* ptr_;  
  19. };  

unique_ptr 可以被放在标准容器中,也可以做 auto_ptr 能做的任意事情,除了隐式地从左值转移。如果你想从左值转移,你只要用 std::move 来传递它即可:

  1. int f(std::unique_ptr<T>);    // accepts a move-only type by value   
  2. unique_ptr<T> x;              // has a name so it's an lvalue   
  3. int a = f( x );               // error! (requires a copy of x)   
  4. int b = f( std::move(x) );    // OK, explicit move  

C++0x中的其它只可转移的类型还包括流类型、线程和锁(新的多线程支持),所有的标准容器都可以持有只可转移的类型。

 

第四篇:再论赋值

这是关于C++中的高效值类型的系列文章中的第四篇。在上一篇中,我们讨论了如何处理右值引用函数参数并介绍了只可转移的类型。这次,我们重温一下转移赋值,并且看看如何才能正确并高效地把它写出来。

在本系列文章的第二篇中,我们示范了一个 vector 转移赋值的实现,但其中隐藏着一个微妙的问题。以下列代码为例:

  1. mutex m1, m2;  
  2. std::vector<shared_ptr<lock> > v1, v2;  
  3. v1.push_back(shared_ptr<lock>(new lock(m1)));  
  4. v2.push_back(shared_ptr<lock>(new lock(m2)));  
  5. v2 = v1;              // #1   
  6. …  
  7. v2 = std::move(v1);   // #2 - Done with v1 now, so I can move it   
  8. …  

赋值#1释放了 v2 所独占的所有锁(并让 v2 成为 v1 所拥有的所有东西的共用者)。但是赋值#2并没有即时效果,除了交换各自的锁拥有权。在#2的情况下,原来由 v2 所持有的锁不会释放,直至 v1 离开其范围域,这可能是很久很久以后。上锁与解锁的顺序与否是正确的多线程程序和死锁之间的区别,所以这是一个严重的问题,我们前面的关于转移赋值的描述需要加以修正:

转移赋值的语义:转移赋值操作符从“偷取”它的参数的值,将该参数置于可析构和可赋值的状态,并保留左操作数的任何用户可见的副作用。

有了以上指引的帮助,现在我们可以修改我们对 std::vector 的转移赋值实现:

  1. vector& operator=(vector&& rhs)  
  2. {   
  3.     this->clear();  
  4.     std::swap(*this, rhs);  
  5.     return *this;  
  6. }  

在实践中,最先的 clear() 通常没有什么作用(也没有什么开销),因为转移赋值的目标通常已经是空的了,通常它本身就是前面某次转移赋值的源对象。在大多数标准算法以及前面我们作为了一个例子的插入排序算法中,这确实是真的。不过,加上这一个 clear() 可以在向一个非空的左操作数进行转移赋值时避免麻烦。

规范性的赋值?

正如前一篇所提到的,复制赋值有一种“规范性的实现”,基于一次复制构造和一次交换:

  1. T& operator=(T rhs) { swap(*this, rhs); return *this; }  

它有很多好处:

  • 很容易写对
  • 与“手工实现”相比,极大地减少了复杂度
  • 利用了复制省略
  • 提供了强异常保证,如果 swap 是无抛出的

只要你实现了转移构造和廉价、无抛出的交换,以上也可以看作是一个好的转移赋值操作符的实现。右值参数可以转移构造至 x 然后交换给 *this。华而不实!如果你已经使用了规范的复制赋值操作符,毕竟你可能就不需要再写一个转移赋值操作符了。

这就是说,即使是在C++03中,这种“规范实现”往往过早地从左值进行复制。对于 std::vector 的情形,在赋值号左边的对象也许有足够的空间,你只需要销毁其中的元素然后将右边的元素复制过来就可以了,这样可以避免一次昂贵的内存分配,而且如果源 vecotr 非常大的话,可能会导致内存不足。

所以,std::vector 使用了一种更为经济的赋值操作符,其签名为 vector& operator=(vector const&),该实现允许将左值的复制延迟至已确认了必须要复制之后。不幸的是,如果我们试图将这个规范的复制赋值签名用于右值的话,将产生歧义的重载。相反,我们需要类似的东西,即效果相同但是将临时对象的生成移至赋值操作符之内:

  1. vector& operator=(vector&& rhs)  
  2. {  
  3.     vector(std::move(rhs))  
  4.       .swap(*this);  
  5.     return *this;  
  6. }  

看上去,这确实就是转移赋值语义的泛型实现,不过这里有另外一个问题。我们来计算一下这个操作的总开销:

  • 第3行:3次内存读和4-6次的内存写(视实现而定)
  • 第4行:6次读和6次写
  • 第5行:*this 原用内容的析构

现在来和“清除后交换”的实现比较一下:

  1. vector& operator=(vector&& rhs)  
  2. {   
  3.     this->clear();  
  4.     std::swap(*this, rhs);  
  5.     return *this;  
  6. }  
  • 第3行:*this 原用内容的析构
  • 第4行:6次读和6次写

回想一下早前我们提到过的,多数(甚至可能是绝大多数)转移赋值的左操作数是一个刚刚被转移走的对象。在一般情形下,析构 *this 原用内容——通过 clear 或是通过临时对象的析构——的开销只是单次的测试和跳转。所以,其它的操作是主要开销,而“清除后交换”的实现要比另一个实现差不多快上两倍。

实际上,我们可能还可以做得更好。是的,理论上 swap 操作可以让我们回收利用左操作数的空间而不是过早地把它处理掉,但实际问题是该什么时候做呢?答案是仅当从一个已被 std::move 的左值进行赋值的时候,因为真正的右值很快会被销毁。那么有多大机会左操作数真的有足够空间呢?机会不大,因为左操作数通常都是一个刚刚被转移走的对象。因此,以下这样的实现有可能是真正最高效的:

  1. vector& operator=(vector&& rhs)  
  2. {   
  3.     if (this->begin_)  
  4.     {  
  5.         this->destroy_all(); // destroy all elements   
  6.         this->deallocate();  // deallocate memory buffer   
  7.     }  
  8.     this->begin_ = rhs.begin_;  
  9.     this->end_ = rhs.end_;  
  10.     this->cap_ = rhs.cap_;  
  11.     rhs.begin_ = rhs.end_ = rhs.cap_ = 0;  
  12.     return *this;  
  13. }  

说的够多了,我要看数字!

如果你想知道以上各种实现的实际效果, 我用了这个测试文件来证明不仅可以通过实现转移语义来提高速度,还可以进一步地优化它。这个测试是对一个 std::vector 和一个 boost::array 使用支持转移语义的 std::rotate 算法。如你所见,对于 std::vector,“规范的”转移语义实现要比“清除后交换”实现好一点点,不过,通过以最少操作实现转移赋值,还可以更快。

一

                     vector 转移赋值的实现比较


                       array 转移赋值的实现比较

对于 boost::array(我们假设其结构类似于 boost::tuple),轮到使用 swap 的实现比最简单的一个一个元素的转移实现差不多慢上三倍,而经过仔细优化,我们还可以做得更好。

因此,这个故事的寓意是:要警惕公式化的转移操作实现;记住,转移语义就是为了优化,所以转移操作必须是非常快的,这里一点开销,那里一点开销,就有可能产生明显的差异。 

 

第五篇 异常安全的转移

 

原文来自:http://cpp-next.com/archive/2009/10/exceptionally-moving/

欢迎来到关于C++中的高效值类型的系列文章中的第五篇。在上一篇中,我们停留在对转移赋值最优实现的不断找寻中。今天,我们将要找到一条穿过这个“转移城市(Move City)”的道路,在这里,最普通的类型都可能有令人惊讶的冲突。

在前面的文章中,我们看到,通过提供”转移的权限”,可以让同一段代码同时用于可转移类型和不可转移类型,并在可能的时候尽量利用转移优化。这种“在可能的时候转移,在必须的时候复制”的方法,对于代码优化是很有用的,也兼容于那些没有转移构造函数的旧类型。不过,对于提供强异常保证的操作来说,却增加了新的负担。

强异常保证,强异常要求

实现强异常保证要求将某个操作的所有步骤分为两类:

  1. 有可能抛出异常但不包含任何不可逆改变的操作
  2. 可能包含不可逆改变但不会抛出异常的操作


强异常保证依赖于对各步操作的分类

如果我们将所有动作分入这两类,且保证任何第1类的动作都在第2类动作之前发生,就没我们什么事了。在C++03中有一个典型例子,当 vector::reserve() 需要为新元素分配内存时:

  1. void reserve(size_type n)  
  2. {  
  3.     if (n > this->capacity())  
  4.     {  
  5.         pointer new_begin = this->allocate( n );  
  6.         size_type s = this->size(), i = 0;  
  7.         try  
  8.         {  
  9.             // copy to new storage: can throw; doesn't modify *this   
  10.             for (;i < s; ++i)  
  11.                  new ((void*)(new_begin + i)) value_type( (*this)[i] );  
  12.         }  
  13.         catch(...)  
  14.         {  
  15.             while (i > 0)                 // clean up new elements   
  16.                (new_begin + --i)->~value_type();  
  17.    
  18.             this->deallocate( new_begin );    // release storage   
  19.             throw;  
  20.         }  
  21.         // -------- irreversible mutation starts here -----------   
  22.         this->deallocate( this->begin_ );  
  23.         this->begin_ = new_begin;  
  24.         this->end_ = new_begin + s;  
  25.         this->cap_ = new_begin + n;  
  26.     }  
  27. }  

如果是在支持转移操作的实现中,我们需要在 try 块中加上一个对 std::move 的显式调用,将循环改为:

  1. for (;i < s; ++i)  
  2.      new ((void*)(new_begin + i)) value_type( std::move( (*this)[i] ) );  

在这点变化中,有趣的是,如果 value_type 是支持转移的,那么在循环中会改写 *this (从左值进行显式的转移请求,是一种逻辑上有改写的操作)。

现在,如果转移操作会抛出异常,这个循环就会产生不可逆转的变化,因为要回滚一个部分完成的循环是需要更多的转移操作。因此,要在 value_type 支持转移的情况下保持强异常保证,它的转移构造函数必须是无抛出的。


  可能有抛出的转移操作不能做到无潜在再次抛出的回滚

结果

C++0x 标准草案中基本上是反对可抛出的转移构造函数的,我们建议你遵守此规则。不过,转移构造函数必须无抛出这条规则并不总是那么容易遵守的。以 std::pair<std::string,UserType> 为例,其中 UserType 是带有可抛出复制构造函数的类型。在 C++03 中,这个类型是没有问题的,可以用在 std::vector 中。但是在 C++0x 中,std::string 带有转移构造函数,std::pair 同样也有:

  1. template <class FirstType, class SecondType>  
  2. pair<FirstType,SecondType>::pair(pair&& x)  
  3.   : first(std::move(x.first))  
  4.   , second(std::move(x.second))  
  5. {}  

这里就有问题了。second 的类型是 UserType,它没有转移构造函数,这意味着 second 的构造是一次(有可能抛出的)复制构造,而不是转移构造。所以,pair<std::string, UserType> 给出的是一个可抛出的转移构造函数,它不能再用于 std::vector 中而不破坏强异常保证了。

今天,这意味着我们需要一些类似于以下代码的东西来令 pair 可用。

  1. template   
  2. pair(pair&& rhs  
  3.   , typename enable_if<                 // Undocumented optional   
  4.         mpl::and_<                      // argument, not part of the   
  5.             boost::has_nothrow_move // public interface of pair.   
  6.           , boost::has_nothrow_move  
  7.         >  
  8.      >::type* = 0  
  9. )  
  10.   : first(std::move(rhs.first)),  
  11.     second(std::move(rhs.second))  
  12. {};  

通过使用 enable_if,可以令到这个构造函数“消失”,除非 has_nothrow_move 对于 T1 和 T2 均为 true。

我们知道,没有办法检测是否存在一个转移构造函数,更不要说它是否无抛出了,因此,在我们得到新的语言特性之前,boost::has_nothrow_move 都是补救的方法之一,它对于用户自定义类型返回 false,除非你对它进行了特化。所以,在你编写一个转移构造函数时,应该对这个 trait 进行特化。例如,如果我们为 std::vector 和 std::pair 增加了转移构造函数,我们还应该加上:

  1. namespace boost  
  2. {  
  3.     // All vectors have a (nothrow) move constructor   
  4.     template <class T, class A>  
  5.     struct has_nothrow_move<std::vector<T,A> > : true_type {};  
  6.    
  7.     // A pair has a (nothrow) move constructor iff both its   
  8.     // members do as well.   
  9.     template <class First, class Second>  
  10.     struct has_nothrow_move<std::pair<First,Second> >  
  11.       : mpl::and_<  
  12.            boost::has_nothrow_move<First>  
  13.          , boost::has_nothrow_move<Second>  
  14.         > {};  
  15. }  

我们承认这很不好看。C++委员会还在讨论如何解决这个问题的细节,不过以下一些事情都已经获得普通同意:

  • 我们不能由于静静地放弃了强异常保证而破坏现有的代码。
  • 可以通过在适当的时候生成缺省的转移构造函数——正如 Bjarne Stroustrup 在 N2904 中所建议的——减小这个问题。这可以修复 pair 以及所有类似类型的问题,同时通过增加生成的转移优化,还可以“免费”提升一些代码的速度。
  • 还是有些类型需要我们“手工”来处理。

“有问题的类型”

归为有问题的类型通常都带有我们想要转移的子对象——已提供了安全实现——和其它一些我们需要“其它操作”的子对象。std::vector 就是一个例子,它带有一个分配器,其复制构造函数有可能会抛出异常:

  1. vector(vector&& rhs)  
  2.   : _alloc( std::move(rhs._alloc) )  
  3.   , _begin( rhs._begin )  
  4.   , _end( rhs._end )  
  5.   , _cap( rhs._cap )  
  6. {  
  7.     // "something else"   
  8.     rhs._begin = rhs._end = rhs._cap = 0;  
  9. }  

一个简单的成员式转移,例如在 N2904 中所说的缺省生成的那个,在这里将不具有正确的语义。尤其是,它不会把 rhs 的 _begin, _end 以及 _cap 置零。但是,如果 _alloc 不具有一个无抛出的转移构造函数,那么在第2行中就只能进行复制。如果该复制可以有抛出异常,那么 vector 提供的就是可抛出的转移构造函数。

对于语言设计者来说,挑战是如何避免要求用户两次提供相同的信息。既要在转移构造函数的签名中指明成员的类型是可转移的(前面的 pair 转移构造函数中的第5、6行),又要在成员初始化列表中再真正对成员进行转移(第10、11行)。目前正在讨论的一个可能性是使用一个新的属性语法,令到 vector 的转移构造函数可以这样写:

  1. vector(vector&& rhs) [[moves(_alloc)]]  
  2.   : _begin( rhs._begin )  
  3.   , _end( rhs._end )  
  4.   , _cap( rhs._cap )  
  5. {  
  6.     rhs._begin = rhs._end = rhs._cap = 0;  
  7. }  

这个构造将被 SFINAE 掉,除非 _alloc 本身具有无抛出的转移构造函数,且这个成员会从 rhs 的相应成员转移过来,从而被隐式地初始化。

不幸的是,对于C++0x中属性的应有作用一直存在一些分歧,所以我们还不知道委员会会接受怎样的语法,但至少我们认为原则上我们已经理解了问题何在,以及如何解决它。

后续

好了,感谢你的阅读;今天就到此为止。下一篇我们将讨论完美转发,还有,我们也没有忘记还欠你一个关于C++03的转移模拟的调查。

 

第六篇 向前!向前

除了提供转移语义,右值引用的另一个主要用途是解决“完美转发”。在这里,“转发”的指将一个泛型函数的实参转发至另一个函数而不会拒绝掉第二个参数可接受的任何参数,也不会丢失关于这些参数的cv限定或左右值属性的任何信息,而且还无须采用重载。在C++03中,最佳的近似是将所有右值变为左值,并且需要两个重载。

为什么要解决这个问题?

考虑以下例子:

  1. template <class F>  
  2. struct unary_function_wrapper  
  3. {  
  4.      unary_function_wrapper(F f) : f(f) {}  
  5.    
  6.      template <class ArgumentType>  
  7.      void operator()( ArgumentType x ) { f(x); }  
  8.  private:  
  9.      F f;  
  10. };  

这种方式是不行的,因为我们的函数调用操作符是传值的,它会拒绝所有不可复制/不可转移的类型,即便 f 可以接受这些类型。如果我们把它改为:

  1. template <class ArgumentType>  
  2. void operator()( ArgumentType& x ) { f(x); }  

那么我们会拒绝所有非常量性的右值。我们可以加一个重载:

  1. template <class ArgumentType>  
  2. void operator()( ArgumentType& x ) { f(x); }  
  3. template <class ArgumentType>  
  4. void operator()( ArgumentType const& x ) { f(x); }  

但是这也和前面的一样,会丢失右值性,而我们本想保留它的,以便 f 可以利用那些我们在前面几篇文章中讨论过的转移优化。引入第二个重载还会带来另一个问题:它不能扩展至多个参数的情形。一个 binary_function_wrapper 就需要四个重载,再来一个 ternary_function_wrapper 就需要八个重载了,完美转发 n 个参数需要 2^n 个重载。

可行的解决方案

对于右值引用,我们可以利用一些特殊设计的语言规则来解决这个问题:

  1. template <class ArgumentType>  
  2. void operator()( ArgumentType && x )  
  3. { f( std::forward<ArgumentType>(x) ); }  

这两个特殊规则是:

  1. 关于右值引用消除的规则。在C++0x中,很早以前就决定了如果 T 为 U&,则 T& 也为 U&。这是左值引用消除。而对于右值引用,规则被更改为:
    1. & + & 变为 &
    2. & + && 变为 &
    3. && + & 变为 &
    4. && + && 变为 &&

    即,任一”左值性”都会令结果变为左值。

  2. 关于推定“全泛化右值引用”参数(如上例中的 ArgumentType)的规则。该规则规定,如果实参是一个右值,则 ArgumentType 被推定为非引用类型,而如果实参是一个左值,则 ArgumentType 则被推定为左值引用类型。

当实参为类型 Y 的右值时,这两个规则的结果是:ArgumentType 被推定为 Y,因此这里只有一个引用且没有被消除:函数的参数类型为 Y&&。

当实参是类型 Y 的左值时,ArgumentType 被推定为 Y& (或 Y const&),且引用消除规则会起作用,使得实例化的函数的参数类型为 Y& && 即 Y&...它刚好被绑定到一个左值。

最后一个要点是 forward 函数,它的任务是,当 ArgumentType 为非引用时,“重建”实参的右值性,从而无干扰地把它传递给 f。

和 std::move 一样,std::forward 是一个零开销的操作。虽然它不是一个真正的转换,但是你可以把 std::forward<ArgumentType>(x) 想象为 static_cast<ArgumentType&&>(x) 的描述性表示:当实参是一个右值时,我们把 x 转型为一个匿名右值引用,但是当实参是一个左值时,ArgumentType 就是一个左值引用且引用消除会起作用,因此 static_cast 的结果仍是一个左值引用。

“forward” 实际上意味着什么?

最近,关于 forward 的定义是否应该进行调整以适用于除“完美转发”以外的用途,产生了一些实质性的异议,这些其它用途包括:用于类似于 std::tuple 这样的类型的转移构造函数,这些类型可能含用引用成员,并且要防止将左值引用绑定到右值成员这样的危险情形。被建议的调整通常被称为帮助你“把一个 X 当作一个 Y 来转发”。有关这些调整的细节,请参考 N2951

我从来没有认同过这个方向的调整,因为“转发一个函数的实参并保持它们的cv限定及右值性”对我来说非常有意义,而“把 X 当作 Y 来转发”则没有明显的意义。换句话说,这种 forward 如何使用以及为何要用,并没有明显的心理模型:我们没有与之对应的编程模型。我最终放弃对此的抵抗,这是因为我看到,它在某些用户必须要处理的与类相关的一些问题方面是有用的,但是我仍然认为,我们必须指出它的意义所在,并且把它解释清楚。

  评论这张
 
阅读(1098)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017