TODO
左右值引用,完美转发,RVO,重新修改文章
前言
目前还是初稿,文章有待更改,参考资料都为好文,学习自用,有错麻烦提一下
左右值
左右值判断
-
官方定义:cpprefernce中文和cpp英文
之所以要认识左右值是因为后面要使用移动语义,而我们不需要那么精确的左右值定义,只需要知道变量是左值,其他的:函数返回,字面值等很容易看出来是右值
一些观念
-
等号左边是左值,等号右边是右值
这是最初的定义
-
看能不能对表达式取地址,如果能,则为左值,否则为右值
移动语义
右值是可以很安全的被"移动"的,operator=赋值的时候调用移动构造函数,比左值赋值时候调用的拷贝构造函数更高效
移动语义就是
移动语义的原理
略,只是类似于简单的交换指针一样
std::move作用:
将左值转换为右值,从而方便应用移动语义
使用场景和方法:
-
成员函数中shared_ptr的传参并复制给类成员变量
用shared_ptr要拷贝,一个shared_ptr对象,因为shared_ptr对象是线程不安全的,在这里用到shared_ptr会有问题(在构造函数时线程不安全吗),用移动构造函数就不会了(为什么呢TODO)
-
unique_ptr,见下面[不可复制对象用移动]部分
-
void push_back( const T& value ); // (1) void push_back( T&& value ); // 定义赋值函数 vector<vector<string>> vv; vector<string> v = {"123", "456"}; v.push_back("789"); // 临时构造的string类型右值被移动进容器v vv.push_back(move(v)); // std::move的用法,显式将v移动进vv
-
同样是使用vector的情况
class Mystring;//详细定义略,假定他的占用很大 MyString(MyString&& str) noexcept{};//移动构造函数,详细内容略 vector<MyString> vecStr; vecStr.reserve(1000); //先分配好1000个空间,不这么做,调用的次数可能远大于1000 for(int i=0;i<1000;i++){ vecStr.push_back(MyString("hello")); } //这里使用移动语义的应用就是定义一个移动构造函数, //这样编译器会自动优化,利用移动构造函数而不是拷贝构造来传入vector中
移动语义的作用
使用移动来代替构造更高的效率
情况一:
vector<string> str_split(const string& s) {
vector<string> v;
// ...
return v; // v是左值,但优先移动,不支持移动时仍可复制。
}
情况二:
//同理上面,用移动构造函数来代替拷贝构造函数,用移动赋值代替复制赋值
vector<string> str_split(const string& s);
vector<string> v = str_split("1,2,3"); //1
vector<string> v2; //2
v2 = str_split("1,2,3");//3
对于上面这段代码,没有移动语义的时候应该是:
-
第一句,栈上构造个v(原答案说是堆上,为什么),然后用拷贝构造函数构造临时值后,拷贝构造给v,再析构临时对象
-
第三句,拷贝构造临时对象后,先清理v2原本的数据,再拷贝赋值给v2,析构释放临时对象内存
如果使用移动语义后:
-
第一句,函数返回的vector用以移动构造对象v。而v不需要额外申请空间,直接取走临时对象的堆上内存(临时对象可以是堆上的吗?),之后临时对象成为空壳,析构时也无需释放堆内存。
-
第三句,返回值移动构造给v2,v2原本的数据先被清理掉,然后不用释放内存
情况三:
std::vector的增长
当vector的存储容量需要增长时,通常会重新申请一块内存,并把原来的内容一个个复制过去并删除。复制并删除,这里改用移动语义就够了。对vector<string>这样的容器带来性能改变巨大
不可复制对象用移动
不可复制对象某些情况下可以使用移动语义,std::thread和unqiue_ptr
unique_ptr<SomeObj> create_obj(/*...*/) {return unique_ptr<SomeObj>(new SomeObj(/*...*/));
}//返回unqiue_ptr,因为函数传参返回要用到拷贝构造,只能移动语义
-
比如把unque_ptr放入vector中,因为vector重新分配空间时会复制对象到另一块内存,所以只能移动
额外知识
回值优化(RVO/NRVO, RVO, Return Value Optimization 返回值优化,或者NRVO, Named Return Value Optimization)
可见参考资料5,TODO找出inside cpp object model的例子
完美转发std::forword
简单的说,与std::move区别就是,move会把参数转换成右值,而std::forward是把参数保留左右值
应用场景:
template <typename T>//(1)
void relay(T&& t) { func(t);}
////////
template <typename T>//(2)
void relay(T&& t) { func(std::forward<T>(t));}
以上这个realay函数作用是把参数传进func并调用,在一版本中,因为t是具名的,所以编译器认为他是一个左值,调用的时候会传入左值
TODO:给个回调函数的例子