例2 auto场景
Widget&& var1 = someWidget; // var1 is of type Widget&& (no use of auto here)
auto&&是万引用 传入左值var1,发生自动推导和引用折叠。auto&&被推导为左值引用Widget&
auto&& var2 = var1; // var2 is of type Widget& (see below)
例3 typedef场景
template<typename T>
class Widget {
typedef T& LvalueRefType;
typedef T&& RvalueRefType;
...
};
int main() {
Widget<int&> w;
}
注意万能引用是万能引用,引用折叠是引用折叠。并不是说引用折叠是万能引用专属的特性。上面这个例子就只发生了引用折叠
除了右值引用的右值引用会折叠为右值引用,所有其他种类的”引用的引用” (i.e., 组合当中含有lvalue reference) 都会折叠为 lvalue reference。
推导LvalueRefType,因为传入的T类型实例化为int&,这里T&出现的就是左值引用的左值引用(int& &),这里出现的T&&就是左值引用的右值引用(int& &&) 根据规则都会发生引用折叠变为左值引用int&。—-T==int&(就是这么实例化的) T&== LvalueRefType==int&(因为左值引用的左值引用 发生了引用折叠 变回了左值引用) T&&==RvalueRefType==int&(因为左值引用的右值引用 发生了引用折叠 变回了左值引用)
如果我们只用这些typedef,一样会发生引用折叠
void f(Widget<int&>::LvalueRefType&& param);
//对typedef扩展void f(int& && param);---左值引用的右值引用发生引用折叠变为
void f(int& param);
void f(Widget<int&>::RvalueRefType&& param);
//--左值引用(RvalueRefType在上面的推导中也变为int&)的右值引用发生引用折叠变为
void f(int& param);
例4 decltype场景
和模板和 auto 一样,decltype 对表达式进行类型推导时候可能会返回 T 或者 T&,然后decltype 会应用 C++11 的引用折叠规则。
decltype 的类型推导规则其实和模板或者 auto 的类型推导不一样。这里的细节过于晦涩
在这里先注意一个点
decltype 对一个具名的、非引用类型的变量,会推导为类型 T ,在相同条件下,模板和 auto 却会推导出 T&。
decltype((变量))双层括号的结果永远是引用类型,而decltype(变量)的结果只有当变量本身是一个引用时才会返回引用类型
decltype 进行类型推导只依赖于 decltype括号内的表达式; 用来对变量进行初始化的表达式的类型(如果有的话)会被忽略。—-即不看用于初始化的实参,只着眼于decltype括号内的表达式
如下例
Widget w1, w2;
auto&& v1 = w1;
//auto&&是万能引用,传入w1是左值 推导T为Widget &,
//Widget& &&发生引用折叠变为Widget&。
decltype(w1)&& v2 = w2;
//w1为具名非引用变量,推导为T为类型Widget ,Widget &&正常 是个右值引用,
//decltype推导类型并不会关心由于初始化v2变量的实参w2的类型,
//只关心decltype括号里的内容
//---因为右值引用只能绑定右值,而这里给了个左值所以编译出错
std::move()与std::forward()源码剖析
在分析std::move()与std::forward()之前,先看看removereference,下面是removereference的实现:
template<typename _Tp>
struct remove_reference
{ typedef _Tp type; };
//如果传入_Tp为
// 特化版本 特化了_Tp& _Tp&&的struct 对于struct中的参数_Tp是确定
template<typename _Tp>
struct remove_reference<_Tp&>
{ typedef _Tp type; };
template<typename _Tp>
struct remove_reference<_Tp&&>
{ typedef _Tp type; };
/**类模板特例化和部分特例化
//原始的,最通用版本,允许接收T为任何类型
template<typename T>
class C { ... };
//部分特例化,此版本仅适用于指针类型
template<typename T>
class C<T *> { ... };
注:只有类模板才支持部分特例化,函数模板特例化时必须为原模板中每个模板参数都提供实参。 类模板部分特例化本身仍然是一个模板,使用它时用户还必须为那些再部分特例化版中未指明的模板参数提供实参。上面这个例子中,特例化版本的模板参数数量与原始模板相同,但是类型不同。
c
c
template<typename _Tp>
struct remove_reference<_Tp&>
template<typename _Tp>
struct remove_reference<_Tp&&>
同理就是部分特例化,当特例化
template <typename T>
class Test{
public:
void print(){
cout << "General template object" << endl;
}
};
template<> // 对int型特例化
class Test<int>{
public:
void print(){
cout << "Specialized template object" << endl;
}
};
另外,与函数模板不同,类模板的特例化不必为所有模板参数提供实参。我们可以只指定一部分而非所有模板参数,这种叫做类模板的偏特化 或部分特例化(partial specialization)。例如,C++标准库中的类vector的定义:
template <typename T, typename Allocator>
class vector
{
/*......*/
};
// 部分特例化
template <typename Allocator>
class vector<bool, Allocator>
{
/*......*/
};
在vector这个例子中,一个参数被绑定到bool类型,而另一个参数仍未绑定需要由用户指定。注意,一个类模板的部分特例化版本仍然是一个模板,因为使用它时用户还必须为那些在特例化版本中未指定的模板参数提供实参。
/**类模板特例化和部分特例化
remove_reference的作用是去除T中的引用部分,只获取其中的类型部分。无论T是左值还是右值,最后只获取它的类型部分。
注意C++的类型推导(auto decltype typedef 万能引用模板 引用折叠)是在编译时进行,编译完就明确知道调用的函数具体的所有细节
预备知识
引用折叠规则:
X& + & => X&
X&& + & => X&
X& + && => X&
X&& + && => X&&
函数模板参数推导规则(右值引用参数部分):
当函数模板的模板参数为T而函数形参为T&&(右值引用)时适用本规则。
若实参为左值(U,U&,U&&的有名左值 等)则认为其类型为U& ,则模板参数 T 应推导为引用类型 U& 。
(根据引用折叠规则, U& + && => U&, 而T&& ≡ U&,故T ≡ U& )
若实参为右值(U(),move(左值),static_cast左值 等)则认为其类型为U&& ,则模板参数 T 应推导为非引用类型 U 。
(根据引用折叠规则, U或U&& + && => U&&, 而T&& ≡ U&&,故T ≡ U,这里强制规定T ≡ U )
std::remove_reference为C++0x标准库中的元函数,其功能为去除类型中的引用。
std::remove_reference::type ≡ U
std::remove_reference::type ≡ U
std::remove_reference::type ≡ U
以下语法形式将把表达式 t 转换为T类型的亡值(右值的一种)static_cast
非引用类型的转型表达式,如static_cast
static_cast
static_cast
static_cast<新类型> 如果新类型是对象类型的右值引用则返回亡值(右值的一种),新类型是左值引用(或者是某个函数类型的右值引用很奇怪也很少见)则返回左值,除去这两种情况其他新类型cast返回的都是纯右值。
static_cast —-会根据模板类型调用_Tp的拷贝构造,移动构造 or 参数构造,或者引用类型转换(不调构造) 来完成对应的类型转换
对于对象的引用必须是左值(常量引用除外)const引用能够绑定到临时对象, 并将临时对象的生命周期由”创建临时对象的完整表达式”提升至”绑定到的const引用超出作用域”。
///////函数返回引用
引用初始化绑定一个指定对象,且中途不可更改绑定对象
用引用作函数的返回值的最大的好处是在内存中不产生返回值的副本
float area;
float fn1(float r){
area=r*r*3.14;
return area;
}
float &fn2(float r){ //&说明返回的是area的引用,换句话说就是返回area本身
area=r*r*3.14;
return area;//area是全局变量 在实际使用中返回局部变量的引用是没有意义的
//因为局部变量在函数结束后就析构了,return的是个无意义的乱码
}
返回正常对象
fn1返回 area 编译器会创建一个临时对象 返回值类型temp = area — 相当于初始化构造,根据area类型取调用返回值类型对于的拷贝构造或移动构造,构造完临时对象temp后函数结束,area是局部对象的话就析构了。 如果有对象接受了返回的temp则会调用 移动构造(如果有的话)进行构造,或者接受对象已经存在 则会调用移动赋值。 一般编译器会做返回值优化,一般第一次构造temp都可以免了
返回引用
float c=fn2(5.0);//函数返回引用 用于初始化对象
fn2返回area 因为返回类型是引用类型所以不会创建临时对象temp,直接将area返回初始化(拷贝构造或移动构造)对象c(注意c是用area构造的新对象 而非area的别名)。函数返回引用避免了临时对象的产生,免去了调用构造函数 用temp构造c的过程。
float &d=fn2(5.0);//函数返回引用 用于初始化引用
fn2返回area 因为返回类型是引用类型所以不会创建临时对象temp,直接将area返回初始化对象的引用d,由于area是全局变量,所以在temp的生命周期内d一直有效,所以这种做法是安全的。
返回类成员的引用时最好返回const引用,这样可避免在无意的情况下破坏该类成员。
并且因为返回的是引用,可以将fn2函数作为左值 fn2(1) = 0;—函数返回的是area的引用 间接修改 area的值
现在的c++14 17自动带有返回值优化RVO 能够达到和返回引用一样的效果,不需要创建返回对临时对象temp
//////函数返回引用
template <class T>
typename tinySTL::remove_reference<T>::type&& move(T&& t) noexcept {
using return_type = typename tinySTL::remove_reference<T>::type&&;
return static_cast<return_type>(t);
}
“typename在这里有什么用?” 目的是告诉编译器后面的type是个类型。因为模板在实例化之前可以认为前面remove_reference
当传入左值 X a; X b = move(a);
move(a) 首先根据a的类型来进行推导,传入a为X类型,因为实参a为左值,则模板参数 T 应推导为引用类型 X&—T==X&(万能引用推导规则) ,则根据万能引用规则T&& == X&(X& &&==X&万能引用自动推导为左值引用) 。
typename tinySTL::remove_reference
typename tinySTL::remove_reference
static_cast
根据标准 static_cast
move返回一个无名右值 X b = 无名右值;//调用X的移动构造函数构造b
当传入右值 X b = move(X());
move(X())首先根据X()的类型来进行推导传入X()为X类型,因为实参X()为右值,则模板参数T应推导为分引用类型X—T==X(万能引用推导规则),则根据万能引用规则T&& == X&&万能引用自动推导为右值引用) 。
typename tinySTL::remove_reference
typename tinySTL::remove_reference
static_cast
根据标准 static_cast
move返回一个无名右值 X b = 无名右值;调用X的移动构造函数构造b
无论用左值a还是右值X()做参数来调用std::move函数,
该实现都将返回无名的右值引用(函数返回右值),符合标准中该函数的定义。
使用者使用了forward,则在编译阶段forward的两个函数版本的都能直接推导出比如,
std::forward
int&&(int&&&&) forward(int& t) int&&(int&&&&) forward(int&& t) 两个实例化版本
。在有了两个实例化的函数版本后,再根据传入值类型来决定调用哪个版本的forward
使用remove_reference得到类型T的真正的右值引用类型,将传入的t强转为右值引用并返回。实际上,std::move()并不会move,仅仅做了类型转换而已。真正的移动操作是在移动构造函数或者移动赋值操作符中发生的。
std::forward
运行中 forward中传入是左值的版本
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); }
先通过remove_reference获得类型type,定义__t为type& 左值引用类型,再通过static_cast强制转换,其中_Tp&&是万能引用,当_Tp传入左值引用(int&) 则_Tp&&(int& &&)发生引用折叠变为_Tp&(int&) 左值引用。forward返回左值引用
forward中传入是右值的版本
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
" substituting _Tp is an lvalue reference type");
return static_cast<_Tp&&>(__t);
}
当_Tp被推导为右值引用(int&&),则_Tp&&(int&& &&)则是唯一一种推导为右值引用的引用折叠情况,b_Tp&&(int&&)
例子
void inner(const X&) {cout << "inner(const X&)" << endl;}
void inner(X&&) {cout << "inner(X&&)" << endl;}
template<typename T>
void outer(T&& t) {inner(forward<T>(t));}
outer(a);
outer(X());
inner(forward<X>(X()));
outer(a),a为X类型的左值,因为T&&为万能引用,传入的实参a为左值所以依照万能引用规则 T==X&,T&&==X&,函数原型变为outer(X& t)(t为指向a的左值引用), forward
根据forward实例化模板类型,先在编译时执行的函数实例化,再根据实例化后的哪个forward形参类型和传入的实参t一样,再去调用那个版本。
_Tp == X&
typename std::remove_reference<_Tp>::type == X
typename std::remove_reference<_Tp>::type& == X&
typename std::remove_reference<_Tp>::type&& == X&&
两个forward版本实例化为
X& forward(X& t)
X& forward(X&& t)
因为传入的t为有名左值所以调用第一个形参为左值引用类型版本的forward
执行static_cast<_Tp&&>(t) == static_cast
inner(forward
static_cast<新类型> 如果新类型是对象类型的右值引用则返回亡值(右值的一种),新类型是左值引用(或者是某个函数类型的右值引用很奇怪也很少见)则返回左值,除去这两种情况其他新类型cast返回的都是纯右值。
因为return static_cast
所以调用的是inner(const X&)版本的inner
outer(X()),X()为X类型的右值,因为T&&为万能引用,传入的实参a为右值所以依照万能引用规则 T==X,T&&==X&&,函数原型变为outer(X&& t)(t为指向X()的右值引用), forward
_Tp == X
typename std::remove_reference<_Tp>::type == X
typename std::remove_reference<_Tp>::type& == X&
typename std::remove_reference<_Tp>::type&& == X&&
两个forward版本实例化为
X&& forward(X&
X&& forward(X&& t)
因为传入的实参t为有名右值引用(左值)所以调用第一个形参为左值引用类型版本的forward匹配上X&& forward(X& t) 版本
执行static_cast<_Tp&&>(t) == static_cast
inner(forward
因为return static_cast
所以调用的是inner(X&&)版本的inner
inner(forward
_Tp == X
typename std::remove_reference<_Tp>::type == X
typename std::remove_reference<_Tp>::type& == X&
typename std::remove_reference<_Tp>::type&& == X&&
两个forward版本实例化为
X&& forward(X&
X&& forward(X&& t)
因为传入的实参X()为无名右值引用(右值)所以调用第二个形参为右值引用类型版本的forward匹配上X&& forward(X&& t) 版本
执行static_cast<_Tp&&>(__t) == static_cast
因为return static_cast
所以调用的是inner(X&&)版本的inner