继续阅读之前,你最好了解了左值,右值,左值引用,右值引用等概念。
最好阅读了C++11 move带来的高效
这里我借上一篇C++11 move带来的高效中的CMyString类用一下,代码如下
-
class CMyString
-
{
-
public:
-
CMyString(
char* pStr)
-
: m_pStr(
NULL)
-
, m_nLen(
0)
-
{
-
if (
NULL != pStr)
-
{
-
m_nLen =
strlen(pStr);
-
m_pStr =
new
char[m_nLen +
1];
-
memcpy(m_pStr, pStr, m_nLen);
-
m_pStr[m_nLen] =
0;
-
cout <<
"一般构造函数 str=" << m_pStr <<
endl;
-
}
-
}
-
-
CMyString(
const CMyString& o)
-
: m_pStr(
NULL)
-
, m_nLen(
0)
-
{
-
if (
NULL != o.m_pStr)
-
{
-
m_nLen = o.m_nLen;
-
m_pStr =
new
char[m_nLen +
1];
-
memcpy(m_pStr, o.m_pStr, m_nLen);
-
m_pStr[m_nLen] =
0;
-
cout <<
"拷贝构造函数 str=" << m_pStr <<
endl;
-
}
-
}
-
const CMyString&
operator=(CMyString&& o)
-
{
-
char* pStrTmp = o.m_pStr;
-
int nLen = o.m_nLen;
-
-
o.m_pStr = m_pStr;
-
o.m_nLen = m_nLen;
-
-
m_pStr = pStrTmp;
-
m_nLen = nLen;
-
cout <<
"右值引用类型 重载赋值运算符 str=" << m_pStr <<
endl;
-
return *
this;
-
}
-
const CMyString&
operator=(
const CMyString& o)
-
{
-
if (
this != &o)
-
{
-
if (
NULL != m_pStr)
-
{
-
delete[] m_pStr;
-
m_pStr =
NULL;
-
}
-
m_nLen = o.m_nLen;
-
if (
NULL != o.m_pStr)
-
{
-
m_pStr =
new
char[m_nLen +
1];
-
memcpy(m_pStr, o.m_pStr, m_nLen);
-
m_pStr[m_nLen] =
0;
-
}
-
cout <<
"重载赋值运算符 str=" << m_pStr <<
endl;
-
}
-
return *
this;
-
}
-
-
~CMyString()
-
{
-
if (
NULL != m_pStr)
-
{
-
//cout << "析构函数 str=" << m_pStr << endl;
-
delete m_pStr;
-
}
-
}
-
-
char* GetData()
-
{
-
return m_pStr;
-
}
-
CMyString(CMyString&& o)
-
: m_pStr(
NULL)
-
, m_nLen(
0)
-
{
-
char* pStrTmp = o.m_pStr;
-
int nLen = o.m_nLen;
-
-
o.m_pStr = m_pStr;
-
o.m_nLen = m_nLen;
-
-
m_pStr = pStrTmp;
-
m_nLen = nLen;
-
cout <<
"右值引用类型 拷贝构造函数 str=" << m_pStr <<
endl;
-
}
-
void swap(CMyString& o)
-
{
-
char* pStrTmp = o.m_pStr;
-
int nLen = o.m_nLen;
-
-
o.m_pStr = m_pStr;
-
o.m_nLen = m_nLen;
-
-
m_pStr = pStrTmp;
-
m_nLen = nLen;
-
}
-
private:
-
char* m_pStr;
-
int m_nLen;
-
};
举一个栗子:我们内部有一个CoreFun函数需要一个CMyString对象,CoreFun函数我们不想暴露给客户,所以我们再封装一个ICoreFun来调用CoreFun,代码如下:
-
void CoreFun(CMyString t)
-
{
-
cout <<
"CoreFun" <<
endl;
-
}
-
-
void ICoreFun(CMyString t)
-
{
-
cout <<
"ICoreFun" <<
endl;
-
CoreFun(t);
很显然,ICoreFun函数只是起了一个转发的作用
测试一下:
-
int _tmain(
int argc, _TCHAR* argv[])
-
{
-
CMyString lvalue("hello this is the lvalue");
-
ICoreFun(lvalue);
-
system(
"pause");
-
}
很显然,中间的一次拷贝构造函数是多余的,我们可以通过引用来优化掉:
-
void ICoreFun(CMyString& t)
-
{
-
cout <<
"ICoreFun" <<
endl;
-
CoreFun(t);
-
}
上面我们的ICoreFun函数的参数是一个左值引用,如果我需要传递一个右值进来的时候怎么办呢?例如ICoreFun(CMyString("hello this is the rvalue"));
(注:VS上自定义类对象右值也可以绑定到左值引用上)
难道要再写一个ICoreFun(CMyString&&)的接口吗?有没有什么办法
当我ICoreFun(lvalue);的时候ICoreFun的函数原型是ICoreFun(CMyString&)
当我ICoreFun(CMyString("hello this is the rvalue"));的时候ICoreFun的函数原型是ICoreFun(CMyString&&)
模板是一个很好的选择,如下的模板可以解决这个问题:
-
template <
typename T>
-
void CoreFun(T t)
-
{
-
cout <<
"CoreFun" <<
endl;
-
}
-
-
template <
typename T>
-
void ICoreFun(T&& t)
-
{
-
cout <<
"ICoreFun" <<
endl;
-
CoreFun(t);
-
}
想要理解如上模板的函数中T&&如何实现我们上面的需求的,需要知道C11引入的 “引用折叠规则” ( reference collapsing rules ),有如下两条:
对右值引用的两个规则中的第一个也同样影响了旧式的左值引用。回想在pre-11 C++时,对一个引用取引用是是不被允许的,比如A& &会导致一个编译器错误。相比之下,在C++11中,引入了如下所示的引用折叠规则(reference collapsing rules):
1. A& &变成A&
2. A& &&变成A&
3. A&& &变成A&
4. A&& &&变成A&&
第二条,有一个对应于函数模板中模板参数的特殊的演绎规则,当其模板参数为右值引用类型的时候:
template
void foo(T&&);
下面,这些规则会生效:
当foo被一个类型为A的左值调用时,T会被转化为A&,因此根据上面的引用折叠规则,这个参数类型实际上会变成A&。
当foo被一个类型为A的右值调用是,T会被转化成A,因此这个参数类型实际上会变成A&&。
现在我们根据上面的规则来推到一下,T&&是怎么实现
当我ICoreFun(lvalue);的时候ICoreFun的函数原型是ICoreFun(CMyString&)
当我ICoreFun(CMyString("hello this is the rvalue"));的时候ICoreFun的函数原型是ICoreFun(CMyString&&)的:
运行程序看一下效果:
-
int _tmain(
int argc, _TCHAR* argv[])
-
{
-
CMyString lvalue("hello this is the lvalue");
-
-
ICoreFun(lvalue);
-
cout <<
endl;
-
ICoreFun(CMyString(
"hello this is the rvalue"));
-
-
system(
"pause");
-
}
这里可以思考了,ICoreFun参数是右值的时候,拷贝构造函数是可以用 右值类型的拷贝构造函数优化的,回顾上一篇move带来的高效中,我们掌握了一种优化方式:对于使用临时对象来构造另一个对象的时候,完全可以通过右值类型的拷贝构造函数中的高效交换来实现,而不是调用拷贝构造函数重新进行new delete操作,因为临时对象马上就要销毁了,所以我们可以只是使用指针的交换来实现了构造的效果。
但是虽然ICoreFun(CMyString("hello this is the rvalue")); 转换为了ICoreFun(CMyString&& t),但是,t是一个左值,所以CoreFun(t);的时候调用的是拷贝构造函数来构造t,
t是一个左值都能理解吧,就和
int&& r = 8;
8是一个右值,r的类型是右值引用,r本身是一个左值,是一个道理的
全都是因为增加了一层转发,CMyString("hello this is the rvalue")从右值变为了左值
有没有办法,在上面的情况中,在CMyString&& t 传递给CoreFun(t)的时候,CoreFun(t)把t当作左值来处理呢?也就在t是CMyString&&类型的时候把t转换为右值,在t是CMyString& t类型的时候不转换。forward就是这个作用。
forward作用:获取参数的原有类型(不太形象,具体看总结中的定义去理解下)
代码修改如下:
-
template <
typename T>
-
void CoreFun(T t)
-
{
-
cout <<
"CoreFun" <<
endl;
-
}
-
-
template <
typename T>
-
void ICoreFun(T&& t)
-
{
-
cout <<
"ICoreFun" <<
endl;
-
CoreFun(
std::forward
(t));
-
}
运行结果:
转发左值就调用拷贝构造函数,抓发右值就调用右值类型的拷贝构造函数,完美。
std::forward argument: Returns an rvalue reference to arg if arg is not an lvalue reference; If arg is an lvalue reference, the function returns arg without modifying its type.
std::forward:This is a helper function to allow perfect forwarding of arguments taken as rvalue references to deduced types, preserving any potential move semantics involved.
std::forward
std::move是无条件的转为右值引用,而std::forward是有条件的转为右值引用,更准确的说叫做Perfect forwarding(完美转发),而std::forward里面蕴含着的条件则是Reference Collapsing(引用折叠)。
std::move不move任何东西。std::forward也不转发任何东西。在运行时,他们什么都不做。不产生可执行代码,一个比特的代码也不产生。
std::move和std::forward只是执行转换的函数(确切的说应该是函数模板)。std::move无条件的将它的参数转换成一个右值,而std::forward当特定的条件满足时,才会执行它的转换。
std::move表现为无条件的右值转换,就其本身而已,它不会移动任何东西。 std::forward仅当参数被右值绑定时,才会把参数转换为右值。 std::move和std::forward在运行时不做任何事情。