先解释一下什么是不完美的转发
int a = 0;
//process内容cout一句话 forward内容cout一句话并调用process
void forward(int&& i){cout<<”xxx”<<endl; process(i);}
process(a);//调用void process(int&i) a被视为左值
process(1);//调用 void process(int&& i) 1是字面值 是匿名的是个右值 调用右值引用的版本
process(move(a));//调用 void process(int&& i) 通过move强制将左值a变为右值 调用函数的右值版本
forward(2);//1 调用forward(int&& i) 因为2是右值 2 调用process(int&)
问什么不调用process(int&& i),因为2是个右值被复制给了形参int&& i,i的类型是右值引用但是i是有名的(named object)!!! i变量是左值!!!, 在调用process时是 process(i);这样来调用i是左值所以肯定调用的是process的左值引用版本process(int&)。
所以我们上面说c.insert(ite,Vtype(buf)); 是个不完美转发,是先调用insert(…,&&x),给匿名对象 Vtype(buf)赋名x,接下来因为x有名了所以insert内以x做形参的其他函数都不会调用&&x右值引用版本,但我们本意是 x就是右值,函数内以x为形参的函数都应该将x当作右值,调用x为右值的函数版本。
形参为匿名对象赋名导致其成为左值 就是不完美转交的根本原因。
forward(move(a)); //1 调用forward(int&& i) 因为move(a)是右值 2 调用process(int&)
//因为是process(i)有名地调用i所以调用的是process的左值引用版本process(int&)原因和上面的一样
forward(a);//编译出错 forward只写了 右值引用版本但是a是有名的,但又没写forward的其他版本所以出错
const int&b=1;//常引用 绑定一个常量
process(b);//编译出错 缺少process(const int&)版本
process(move(b));//编译出错 缺少process(std::remove_reference<const int&>::type)版本
Perfect Forwarding(完美转发)
自己在写右值移动版本的函数时要注意这点
template<typename T1,typename T2>
void functionA(T1&& t1,T2&& t2)
{
functionB(std::foward<T1>(t1),std::forward<T2>(t2));//这样t1,t2将依旧保持右值身份
}
std::forward源码
// 函数重载 传入左值版本
template<typename _Tp>
constexpr _Tp&& foward(typename std::remove_reference<_Tp>::type& __t)noexcept
{return static_cast<_Tp&&>(__t);}//forward传入的是左值type& 将传入变量的类型强制变为右值引用类型
// 函数重载 传入右值版本
template<typename _Tp>
constexpr _Tp&& foward(typename std::remove_reference<_Tp>::type&& __t)noexcept
{
static_assert(!std::is_lvalue_reference<_Tp>::value,”_Tp is an lvalue reference”);
return static_cast<_Tp&&>(__t);
}//forward传入的是右值type&& 将传入变量的类型强制变为右值引用类型
在调用move之前调用forward
move aware class
class MyString{
public:
static size_t Dctor;//默认构造调用次数
static size_t Cctor;//总构造调用次数
static size_t CCctor;//拷贝构造调用次数
static size_t CAsgn;//拷贝赋值调用次数
static size_t Mctor;//移动构造调用次数
static size_t MAsgn;//移动赋值调用次数
static size_t Dtor;//总析构调用次数
private:
char* _data;
size_t _len;
void _init_data(const char*s){//对字符串s 深拷贝
_data = new char[_len+1];
memcpy(_data,s,_len);
_data[_len] = ‘\0’;
//为data申请s那么大的空间+1 并将s内容拷贝到那个堆上内存上
}
public:
MyString():_data(NULL),_len(0){++DCtor;}//默认构造
MyString(const char* p):_len(strlen(p))//带参数的构造
{
++Ctor;
_init_data(p);
}
MyString(const MyString& str):_len(str.len)//拷贝构造
{
++CCtor;
_init_data(str._data);//拷贝
}
MyString(MyString&& str)noexcept //移动构造
:_data(str._data),_len(str.len)
{
++MCtor;
//this偷了str的_data后,str不能再使用data了 所以需要将相关信息清空
str._len = 0;
str._data = NULL;
}
添加
//拷贝赋值
MyString& operator=(const MyString& str)
{
++CAsgn;
if(this!=&str)//自我赋值检查
{
if(_data)delete _data;//先删除 this data之前存的数据
_len = str._len;
_init_data(str._data);
}
return *this;
}
//移动赋值
MyString& operator=(MyString&& str) noexcept
{
++MAsgn;
if(this!=&str)//自我赋值检查
{
if(_data) delete _data;// dctor默认是不抛出错误的 是noexcept的
//偷str的_data
_len = str._len;
_data = str._data;
//this偷了str的_data后,str不能再使用data了 所以需要将相关信息清空
str._len = 0;
str._data = nullptr;
}
return *this;
}
virtual ~MyString{
++Dtor;
if(_data)delete _data;
}
char* get() const {return _data;}
//如果想要将这个类对象装进set容器 需要重载< 和 ==
bool operator<(const MyString& rhs)const{...}
bool operator==(const MyString& rhs)const{...}
};
size_t MyString::Dctor = 0;//默认构造调用次数
size_t MyString::Cctor = 0;//总构造调用次数
size_t MyString::CCctor = 0;//拷贝构造调用次数
size_t MyString::CAsgn = 0;//拷贝赋值调用次数
size_t MyString::Mctor = 0;//移动构造调用次数
size_t MyString::MAsgn = 0;//移动赋值调用次数
size_t MyString::Dtor = 0;//总析构调用次数
//为了unordered容器可以装这个对象 还需要写自己的哈希计算函数
namespace std{//必须放在std里
template<>
struct hash<MyString>{
size_t operator()(const MyString& s)const noexcept{.....}
};
move aware对vector影响很大 对list,multiset红黑树,unorder_multiset哈希表—内容以节点形式存在 影响小
M c1(c);//M是vector<MyString>()容器 — 这里直接调用的是vector这个类的拷贝构造函数
先为this分配对应的空间,再将传入的vector&内的元素内一个一个拷贝到this
所以很耗时 秒级
M c2(move(c));//M是vector<MyString>()容器 — 这里直接调用的是vector这个类的移动构造
核心操作是 _M_swap_data 内调用std::swap将一些vector内的3根指针进行交换 因为c2的这些指针无意义 所以在交换后c获得这些无意义指针,所以在交换后不能再使用c
因为只是交换3根指针,所以移动构造非常快 微秒级
除非设计不允许移动,STL类大都支持移动语义函数,即可移动的。 另外,编译器会默认在用户自定义的class和struct中生成移动语义函数,但前提是用户没有主动定义该类的拷贝构造等函数(具体规则自行百度哈)。 因此,可移动对象在<需要拷贝且被拷贝者之后不再被需要>的场景,建议使用std::move触发移动语义,提升性能。
还有些STL类是move-only的,比如unique_ptr(我们写的smart_ptr),这种类只有移动构造函数,因此只能移动(转移内部对象所有权,或者叫浅拷贝),不能拷贝(深拷贝)