?!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
一. move
关于lvaue和rvalue, 在c++11以前存在一个有的现象QT& 指向lvalueQ?const T&卛_以指向lvalue也可以指向rvalue?/p>
但就是没有一U引用类型,可以限制为只指向rvalue.
q乍h好像也不是很大问题,但事实上q个~陷在有些时候严重的限制了我们在某些情况下,写出更有效率的代码?/p>
举个_子Q假设我们有一个类Q它包含了一些资源:
复制代码
class holder
{
public:
holder()
{
resource_ = new Resource();
}
~holder()
{
delete resource_;
}
holder(const holder& other)
{
resource_ = new Resource(*other.resource_);
}
holder(holder& other)
{
resource_ = new Resource(*other.resource_);
}
holder& operator=(const holder& other)
{
delete resource_;
resource_ = new Resource(*other.resource_);
return *this;
}
holder& operator=(holder& other)
{
delete resource_;
resource_ = new Resource(*other.resource_);
return *this;
}
private:
Resource* resource_;
};
复制代码
q是个RAII的类Q构造函C析构函数分别负责资源的获取与释放Q因此也相应处理了拷贝构造函?copy constructor)和重载赋值操作符(assignment operator)?/p>
现在假设我们q样来用这个类?/p>
// 假设存在如一个函敎ͼq回gؓholdercd
holder get_holder();
holder h;
h = get_holder();
q小D代码的最后一条语句做?件事情:
1) 销毁h中的资源?/p>
2) Lget_holder()q回的资源?/p>
3) 销毁get_holder()q回的资源?/p>
我们昄可以发现q其中做了些不是很有必要的事情,假如我们可以直接交换h中的资源与get_holder()q回的资源,那这h们可以直接省掉第二步中的拯动作了?/p>
而这里之所以交换能辑ֈ相同的效果,是因为get_holder()q回的是临时的变量,是个rvalueQ它的生命周期通常来说很短Q具体在q里Q就是赋D句完成之后,M人都没法再引用该rvalueQ它马上p被销毁了?/p>
如果是像下面q样的用法,我们昄不可以直接交换两者的资源Q?/p>
holder h1;
holder h2;
h1 = h2;
因ؓh2是个lvalueQ它的生命周期较长,在赋D句结束之后,变量q要存在Q还有可能要被别的地方用?/p>
昄Qrvalue的短生命周期l我们提供了在某些情况优化代码的可能?/p>
但这U可能在c++11以前是没法利用到的,因ؓQ我们没法在代码中对rvalue区别对待Q在函数体中Q无法分辨传q来的参数到底是不是rvalueQ缺一个rvalue的标记?/p>
回忆一?T& 指向的是lvalueQ而const T&指向的,却可能是lvalue或rvalueQ没法区分!
Z解决q个问题Qc++11中引入了一个新的引用类型:T&&
q种引用指向的变量是个rvalueQ?有了q个引用cdQ我们前面提到的问题p刃而解了?/p>
复制代码
class holder
{
public:
holder()
{
resource_ = new Resource();
}
~holder()
{
if (resource_) delete resource_;
}
holder(const holder& other)
{
resource_ = new Resource(*other.resource_);
}
holder(holder& other)
{
resource_ = new Resource(*other.resource_);
}
holder(holder&& other)
{
resource_ = other.resource_;
other.resource_ = NULL;
}
holder& operator=(const holder& other)
{
delete resource_;
resource_ = new Resource(*other.resource_);
return *this;
}
holder& operator=(holder& other)
{
delete resource_;
resource_ = new Resource(*other.resource_);
return *this;
}
holder& operator=(holder&& other)
{
std::swap(resource_, other.resource_);
return *this;
}
private:
Resource* resource_;
};
复制代码
q时我们再写如下代码的时候:
holder h1;
holder h2;
h1 = h2; //调用operator(holder&);
h1 = get_holder(); //调用operator(holder&&)
昄后面的实现是更高效的?/p>
写到里,有的Z许提出问? T&& ref 指向的是叛_|那ref本n是左D是右|具体来说是Q?/p>
1 holder& operator=(holder&& other)
2 {
3 holder h = other;//q里调用的是operator=(holder&) q是operator=(holder&&)?
4 return *this;
5 }
q个问题的本质还是怎么区分rvalueQ?/p>
c++11中对rvalue作了明确的定义:
Things that are declared as rvalue reference can be lvalues or rvalues. The distinguishing criterion is: if it has a name, then it is an lvalue. Otherwise, it is an rvalue.
如果一个变量有名字Q它是lvalue,否则Q它是rvalue?/p>
Ҏq样的定义,上面的问题中Qother是有名字的变量,因此是个lvalue,因此W?行调用的是operator=(holder&).
好了说这么久Q一直没说到move()Q现在我们来l出定义Q?/p>
c++11中的move()是这样一个函敎ͼ它接受一个参敎ͼ然后q回一个该参数对应的rvalue().
p么简单!你甚臛_以暂时想像它的原型是q样?当然是错的,正确的原型我们后面再?
T&& move(T& val);
那么Q这样一个move()Q它有什么用呢Q用处大了!
前面用到了std::swap()q个函数Q回想一下以前我们是怎么x实现swap的呢Q?/p>
1 void swap(T& a, T& b)
2 {
3 T tmp = a;
4 a = b;
5 b = tmp;
6 }
惛_一下,如果T是我们之前定义的holderQ这里面多做了多无用功啊,每一个赋D句,有一ơ资源销毁,以及一ơ拷贝!但如果用上了move().
1 void swap(T& a, T& b)
2 {
3 T tmp=move(a);
4 a = move(b);
5 b = move(tmp);
6 }
q样一来,如果holder提供了operator=(T&&)重蝲, 上述操作完全只是交换了3ơ指针,效率大大提升Q?/p>
move使得E序员在有需要的情况下,能够把lvalue当成rvalue来用?/p>
? forward()
1.转发问题
除了move()语义之外Qrvalue的提Z解决另一个问题:转发(forward).
假设我们有这样一个模板函敎ͼ它的作用是:~存一些objectQ必要的时候,创徏新的?/p>
复制代码
template<class TYPE, class ARG>
TYPE* acquire_obj(ARG arg)
{
static list<TYPE*> caches;
TYPE* ret;
if (!caches.empty())
{
ret = caches.pop_back();
ret->reset(arg);
return ret;
}
ret = new TYPE(arg);
return ret;
}
复制代码
q个模板函数的作用简单来_是转发一下参数arglTYPE的reset()函数和构造函敎ͼ除此它就没有再干别的事情Q在q个函数当中Q我们用了g递的方式来传递参敎ͼ昄是比较低效的Q多了次无必要的拯?/p>
于是我们准备Ҏ传递引用的方式Q同时考虑到要能接受rvalue作ؓ参数Q于是改成这P
template<class TYPE, class ARG>
TYPE* acquire_obj(const ARG& arg)
{
//...
}
q样写其实很不灵z:
1)首行Q如果reset() 或TYPE的构造函C接受constcd的引用,那上q的函数׃能用了Q必d外提供非const TYPE&的版本,参数一多的话,很麻烦?/p>
2)其次Q如果reset()或TYPE的构造函数能够接受rvalue作ؓ参数的话Q这个特性在acquire_obj()里头永远也用不上?/p>
其中1)好理解,2)是什么意思?
2)说的是这L问题Q即使TYPE存在TYPE(TYPE&& other)q样的构造函敎ͼ它在acquire_obj()中也永远不会被调用,原因是在acquire_obj中,传递给TYPE构造函数的Q永q是lvalue.
哪怕外面调用acquire_obj()Ӟ传递的是rvalue?/p>
holder get_holder();
holder* h = acquire_obj<holder, holder>(get_holder());
虽然在上面的代码中,我们传递给acquire_obj的是一个rvalueQ但是在acuire_obj内部Q我们再使用q个参数Ӟ它却永远是lvalueQ因为它有名字?/p>
acquire_objq个函数它的基本功能只是传发一下参敎ͼ理想状况下它不应该改变我们传递参数的cdQ假如我们传l它lvalue,它就应该传lvaluelTYPEQ假如我们传rvaluel它Q它应该传rvaluelTYPEQ但上面的写法却没有做到q点Q而在c++11以前也没法做到?/p>
forward()函数的出玎ͼ是Z解决q个问题?/p>
forward()函数的作用:它接受一个参敎ͼ然后q回该参数本来所对应的类型?/p>
比如说在上述的例子中(暂时省略参数的原?后面再介l?:
复制代码
holder* h = acquire_obj<holder, holder>(get_holder());
//假设 acquire_obj()接受了一个rvalue作ؓ参数Q在它的内部Q?/p>
TYPE* acquire_obj(arg)
{
//arg本来是rvalueQ如果我们直接引用,它会被当成lvalue来用?/p>
//但如果我们用forward()处理一下,我们却可以得到它的rvalue版本?/p>
//此处 TYPE的构造函数接受的是一个rvalue?/p>
TYPE* ret = new TYPE(forward(arg));
}
//但如果我们传lacquire_obj()的是一个lvalueQ?/p>
holder h1;
//acquire_obj接受了lvalue作ؓ参数?/p>
acquire_obj<holder,holder>(h1);
TYPE* acquire_obj(arg)
{
//此处,TYPE的构造函数接受的是一个lvalue?/p>
TYPE* ret = new TYPE(forward(arg));
}
复制代码
2. 二个原则
要理解forward()是怎么实现的,先得说说c++11中关于引用的二个原则?/p>
原则(1):
引用折叠原则(reference collapsing rule)
1) T& &(引用的引? 被{化成 T&.
2QT&& &(rvalue的引用)被传化成 T&.
3) T& &&(引用作rvalue) 被{化成 T&.
4) T&& && 被{化成 T&&.
原则(2):
对于以rvalue reference作ؓ参数的模板函敎ͼ它的参数推导也有一个特D的原则:
假设函数原型为:
template<class TYPE, class ARG>
TYPE* acquire_obj(ARG&& arg);
1)如果我们传递lvaluelacquire_obj(), ARG׃被推gؓARG&Q因?/p>
复制代码
ARG arg;
acquire_obj(arg)中acquire_obj被推gؓ
acquire_obj(ARG& &&)
Ҏ前面说的折叠原则Qacquire_obj(ARG& &&)
最后变?/p>
acquire_obj(ARG&)
复制代码
2)如果我们传递rvaluelacquire_obj(),ARG׃被推gؓARG,因此
acquire_obj(get_arg());
则acquire_obj 被推gؓ acquire_obj(ARG&&)
3.l论
有了q两个原则,现在我们可以l出最后acquire_obj的原型,以及forward()的原型?/p>
复制代码
template<class TYPE>
TYPE&& forward(typename remove_reference<TYPE>::type& arg)
{
return static_cast<TYPE&&>(arg);
}
template<class TYPE, class ARG>
TYPE* acquire_obj(ARG&& arg)
{
return new TYPE(forward<ARG>(arg));
}
复制代码
下面我们验证一下,上述函数是否能正常工作,假如我们传给acquire_obj一个lvalueQ根据上面说的模板推导原则,ARG会被推导为ARG&,我们得到如下函数Q?/p>
复制代码
TYPE* acquire_obj(ARG& && arg)
{
return new TYPE(forward<ARG&>(arg));
}
以及相应的forward()函数?/p>
TYPE& &&
forward(typename remove_reference<TYPE&>::type& arg)
{
return static_cast<TYPE& &&>(arg);
}
再根据折叠原则,我们得到如下的函敎ͼ
TYPE* acquire_obj(ARG& arg)
{
return new TYPE(forward<ARG&>(arg));
}
以及相应的forward()函数?/p>
TYPE&
forward(typename remove_reference<TYPE&>::type& arg)
{
return static_cast<TYPE&>(arg);
}
复制代码
所以,最后在acquire_obj中,forwardq回了一个lvalue, TYPE的构造函数接受了一个lvaue, q正是我们所惌的?/p>
而假如我们传递给acquire_obj一个rvalue的参敎ͼҎ模板推导原则Q我们知道ARG会被推导为ARGQ于是得到如下函敎ͼ
复制代码
TYPE* acquire_obj(ARG&& arg)
{
return new TYPE(forward<ARG>(arg));
}
以及相应的forward()函数?/p>
TYPE&&
forward(typename remove_reference<TYPE>::type& arg)
{
return static_cast<TYPE&&>(arg);
}
复制代码
最后acquire_obj中forward()q回了一个rvalueQTYPE的构造函数接受了一个rvalueQ也是我们所惌的?/p>
可见Q上面的设计完成了我们所惌的功能,q时的acquire_obj函数才是完美的{发函数?/p>
?move的原?/strong>
复制代码
template<class T>
typename remove_reference<T>::type&&
std::move(T&& a)
{
typedef typename remove_reference<T>::type&& RvalRef;
return static_cast<RvalRef>(a);
}
复制代码
Ҏrvalue引用的模板推导原则和折叠原则Q我们很Ҏ验证Q无论是lmove传递了一个lvalueq是rvalueQ最l返回的Q都是一个rvalue reference.
而这正是move的意义,得到一个rvalue的引用?/p>
看到q里有h也许会发玎ͼ其实是一个cast嘛,实是这P直接用static_cast也是能达到同L效果Q只是move更具语义|了?/p>