共计 2027 个字符,预计需要花费 6 分钟才能阅读完成。
1. std::forawrd
std::forward<T>(arg)
能够实现完满转发,即如果 arg
是一个右值援用,则转发之后后果仍是右值援用;反之,如果 arg
是一个左值援用,则转发之后后果仍是左值援用.
#include <iostream>
struct BigObject
{char data[1 << 10];
};
void g(BigObject& o)
{std::cout << "lvalue reference\n";}
void g(BigObject&& o)
{std::cout << "rvalue reference\n";}
template <typename T>
void f(T&& arg)
{g(std::forward<T>(arg));
}
int main()
{
BigObject o;
f(o);
f(BigObject());
}
lvalue reference
rvalue reference
2. 为什么须要完满转发?
- 在函数模板编程中,常有一种场景是应用模板参数去调用另一个函数(如,上例中
f
去调用g
),这时候如果只提供值传递版本会显得效率太低。 - 函数的参数个别会尽可能地设为援用类型,以防止对象拷贝带来的昂扬开销.
- 为了使一个函数既能够承受左值,又能够承受右值,C++11 之前的解决方案是将参数类型设为
const Type&
. 但这并不是很不便,如限度了参数是常量. - 如果函数
g
既提供了左值援用版本和右值援用版本,则最好的状况是函数f
能够依据参数类型去调用相应版本的g
. 而完满转发正能够满足此要求.
3. 援用折叠规定
- 右值援用和右值援用叠加将失去右值援用;
- 右值援用和左值援用叠加将失去左值援用;
- 左值援用和左值援用叠加将失去左值援用.
template <typename T>
using TR = T&;
// v 的类型
TR<int> v; // int&
TR<int>& v; // int&
TR<int>&& v; // int&
template <typename T>
using TRR = T&&;
// v 的类型
TRR<int> v; // int&&
TRR<int>& v; // int&
TRR<int>&& v; // int&&
4. 完满转发的原理
template< class T >
T&& forward(typename std::remove_reference<T>::type& t) noexcept;
template< class T >
T&& forward(typename std::remove_reference<T>::type&& t) noexcept;
std::remove_reference<T>
的作用就是去掉 T
中的援用,它是通过模板特化来实现:
template< class T > struct remove_reference {typedef T type;};
template< class T > struct remove_reference<T&> {typedef T type;};
template< class T > struct remove_reference<T&&> {typedef T type;};
根据上述援用折叠规定,如果 T
是 int&
,则 T&&
即为 int&
;反之,如果 T
是 int&&
,则 T&&
为 int&&
.
5. forwarding reference
上例函数 f
中的 T&&
实际上被称为 forwarding reference. 它是一种非凡类型的援用,它保留了函数参数的值类别(category),使得能够通过 std::forward
来转发它.
forwarding reference 包含以下两种:
-
在函数模板中,没有被
const
或volatile
润饰的、申明为右值援用的类型形参:template<class T> int f(T&& x) { // x is a forwarding reference return g(std::forward<T>(x)); // and so can be forwarded } template<class T> int g(const T&& x); // x is not a forwarding reference: const T is not cv-unqualified
-
auto&&
,但如果它跟着一个花括号括起的初始值列表,则它不是 forwarding reference:auto&& vec = foo(); // foo() may be lvalue or rvalue, vec is a forwarding reference g(std::forward<decltype(vec)>(vec)); // forwards, preserving value category auto&& z = {1, 2, 3}; // *not* a forwarding reference (special case for initializer lists) for (auto&& x: f()) {// x is a forwarding reference; this is the safest way to use range for loops}
正文完