此部分内容和右值引用重复
5.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
struct remove_reference<_Tp&>
template
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 万能引用模板 引用折叠)是在编译时进行,编译完就明确知道调用的函数具体的所有细节
/**函数返回引用
引用初始化绑定一个指定对象,且中途不可更改绑定对象
用引用作函数的返回值的最大的好处是在内存中不产生返回值的副本
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返回的都是纯右值。
static_cast —-会根据模板类型调用_Tp的拷贝构造,移动构造 or 参数构造 ,或者引用类型转换(不调构造) 来完成对应的类型转换
因为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
返回引用一般有这些目的:
(1) 访问某个参数(以引用传入,或是 *this )的一部分。这一部分可以是逻辑上的,并不一定是子对象,比如容器的元素。
(2) 访问某个名字不可在外部访问的对象。比如单例的对象。
(3) 较少用:返回参数对象本身,但改变其 cv 限定或值类别。 std::move 、 std::forward 、 std::as_const 为此类。——-就是为了返回参数对象(实参对象)本身所以forward move的返回值类型定义为引用