条款24:区分通用引用和右值引用
1 通用引用和右值引用
- T&& 既可以表示右值引用,也可以表示通用引用。
1)右值引用仅绑定到右值,主要用于识别可以移动的对象。
2)通用引用在源代码中看起来像右值引用,但行为可以类似于左值引用,并且可以绑定到各种类型的对象,包括const、volatile修饰的对象。
void f(Widget&& param); // 右值引用
Widget&& var1 = Widget(); // 右值引用
auto&& var2 = var1; // 不是右值引用
template<typename T>
void f(std::vector<T>&& param); // 右值引用
template<typename T>
void f(T&& param); // 不是右值引用
- 通用引用出现的两种情况:第一个,也是最最常见的,是用于函数模板参数:
template<typename T> void f(T&& param); // param 是一个通用引用
第二个是auto声明:
auto&& var2 = var1; // var2 是一个通用引用
这两种情况的共同点是存在类型推导。
- 与此相比,如果看到没有类型推导的T&&,那么就是右值引用:
void f(Widget&& param); // 没有类型推导; param 是一个右值引用
Widget&& var1 = Widget(); // 没有类型推导; var1 是一个右值引用
- 通用引用是引用,必须进行初始化。
- 如果初始化值是右值,则通用引用对应右值引用;如果初始化值是左值,则通用引用对应左值引用。例如,在函数模板f中,param是一个通用引用。如果将左值w传递给f,则param的类型是Widget&(即左值引用)。如果将std::move(w)传递给f,则param的类型是Widget&&(即右值引用)。
template<typename T>
void f(T&& param); // param 是通用引用
Widget w;
f(w); // 左值传给 f; param 的类型是 Widget&
f(std::move(w)); // 右值传给 f; param 的类型是 Widget&&
- 要使引用成为通用的,类型推导是必要的,但这还不够。引用声明的形式必须只能是:T&&。
template<typename T>
void f(std::vector<T>&& param); // param 是一个右值引用
当调用 f 时,类型 T 将被推导。但是 param 的类型声明形式不是T&&,而是std::vector&&。因此,param 是一个右值引用,如果尝试将一个左值传递给 f:
std::vector<int> v;
f(v); // 错误!不能将左值绑定到右值引用
- 即使存在一个简单的 const 限定符也足以使引用失去通用性:
template<typename T>
void f(const T&& param); // param 是一个右值引用
- 即使在模板中看到一个函数参数的类型是T&&,也不能假设它是通用引用,因为在模板中并不一定存在类型推导。考虑一下 std::vector 中的 push_back 成员函数:
template<class T, class Allocator = allocator<T>> // 来自 C++ 标准
class vector {
public:
//push_back 不能没有特定的vector实例化而存在,而该实例化的类型完全决定了 push_back 的声明void push_back(T&& x);...
};
std::vector<Widget> v; // 会导致 std::vector 模板如下实例化:
class vector<Widget, allocator<Widget>> {
public:
void push_back(Widget&& x); // 右值引用
...
};
push_back 没有使用类型推导。std::vector 中概念上类似的 emplace_back 成员函数确实采用了类型推导:
template<class T, class Allocator = allocator<T>> // 仍然来自C++标准
class vector {
public:
//在这里,类型参数 Args 与 vector 的类型参数 T 是独立的,因此 Args 必须在每次调用 emplace_back 时进行推导template <class... Args>void emplace_back(Args&&... args);...
};
- 通用引用的形式必须是T&&。没有要求使用名称 T。例如,下面的模板采用通用引用,因为形式(type&&)是正确的,并且 param 的类型将被推导(排除调用者明确指定类型的边角情况):
template<typename MyTemplateType> // param 是一个通用引用
void someFunc(MyTemplateType&& param);
- 使用类型 auto&& 声明的变量是通用引用,因为类型推导会发生,并且它们具有正确的形式(T&&)。auto 通用引用在 C++14 中出现的频率要比在C++11中高得多,因为 C++14 中的 lambda 表达式可以声明 auto&& 参数。例如,如果想编写一个 C++14 lambda 来记录任意函数调用所花费的时间:
auto timeFuncInvocation =
[](auto&& func, auto&&... params) {// C++14start timer;std::forward<decltype(func)>(func)( // 使用params调用funcstd::forward<decltype(params)>(params)... );//停止计时器并记录运行时间;
};
1)func 是一个通用引用,可以绑定到任何可调用的对象、左值或右值。
2)args 是通用引用参数包,可以绑定到任意数量的任意类型的对象。
3)timeFuncInvocation 几乎可以对于任何函数的执行进行计时。
2 要点速记
- 如果函数模板参数的类型为 T&&,并用于推导类型 T,或者如果使用 auto&&声明对象,则该参数或对象是通用引用 。
- 如果类型声明的形式不是精确的 type&&,或者如果没有发生类型推导,则 type&&表示右值引用。
- 如果通用引用用右值初始化,则得到右值引用。如果用左值初始化,则得到左值引用。
