这次我真的懂了....
首先 C++11 引入了右值引用 &&
'&&'这个要连起来看, 是一个整体, C++ 多了一个关键字而已.
不是引用的引用. 是船新的一种语法. 那有什么用呢?
额, 参数的类型又多了一种!
- void fun(int T)
- void fun(int& T)
- void fun(int && T)
- void fun(int* t)
之前的参数, 值传递, 引用, 指针. 现在呢? 多了一个叫 "右值引用" 的玩意, 多了一种参数类型的选择. 仅此而已.
那他们号称的右值引用速度快, 代价小呢?
额, 这个需要库作者自己去实现的, 跟 C++ 语言本身无关.
举两个例子
- void fun(int & t)
- {
- t= 2;
- }
- void fun(int && t)
- {
- cout<<"int &&"<<endl;
- int x = t;
- x++;
- x--;
- }
这个右值引用的 fun 函数就更复杂了嘛, 没有说一定要简单啊, 完全由库作者决定的.
当然, 现在的库作者对右值引用的函数往往做了内存转移的操作(尤其是移动构造函数与移动赋值函数)
- class A
- {
- A(int num)
- {
- p = new int(num);
- }
- A(A& a)
- {
- p = new int(*a.p);
- }
- A(A&& a)
- {
- p = a.p;
- a.p = nullptr;
- }
- ~A()
- {
- delete p;
- }
- private:
- int * p=nullptr;
- };
如上, 对于右值引用构造函数, 仅仅是转移了内存, 并让被转移的指针置空. 当然, 这个右值引用构造函数具体的实现还是由库作者决定的.
另外, 如果没有右值引用构造函数, 会自动调用拷贝构造函数.
这里说到了转移, 嗯, 翻译下就是 move.move 这个函数看上去是专门转移内存的. 实际上是错误的..
move 仅仅是进行了一个 右值引用 的强制转换.
对于强制转换, 你可能会写
- template<typename T>
- T && make_move(T&& t) // 当然真正的是 std::move, 我这里取名实现类似的 move.make_move 跟 make_love 没有关系哈, 纯粹的偶然..
- {
- return static_cast<T&&>(t);
- }
额, 这是啥, T && 转换成 T&& , 看上去啥都没做嘛.
首先: 对于 make_move(T&& t)中的 t, 说明 make_move 的函数参数是右值引用, 但不代表 t 是右值引用. t 可能是左值. 额, 越来越头大了.
想起了 "书越读越厚, 然后越读越薄". 其实我自己对这个的理解过程也超过了 2 年多, 这次真的搞懂了!!
上例子缓缓
- int x =10;
- make_move(x) // 此时 x 是左值, 什么叫左值, 就是可以取地址的变量.&x 有意义的变量.
- make_move(20) //20 是真正的右值.
看上去这个时候 make_move 体现出了意义, 把 t 强转成右值引用了.
但读过 模板类型推倒, auto 推导 后, 我们知道, 左值 (或引用) 的强制右值转换返回是个左值引用. 简单的如下:
于是, 经过 make_move 函数后返回的是 int & 而不是 int &&.
那怎么才能得到真正的 int && 呢. 需要加上 Traits.
- template<typename T>
- typename remove_reference<T>::type && make_move(T&& t)
- {
- using Rtype = typename remove_reference<T>::type &&;
- return static_cast<Rtype>(t);
- }
typename 是为了告诉编译器 type 是一个类型, 这个在 stl 很常见.
举个例子
- struct A
- {
- typedef unsigned size_t;
- static size_t value;
- }
我们访问 value 使用 A::value
我们访问 size_t 使用 A::size_t 那么 size_t 到底是值还是类型, 编译器不明白.
所以我们会用 typename A::size_t ; (typename 翻译类型名字, 就是表明该变量是个类型)
remove_reference<T>::type 就是去掉 T 的引用后的类型, 再加上 &&
就是真的 T 的右值引用了.
如你所见, 这个也基本是 std::move 干的事. 因此 move 并没有转移内存还是啥的, 甚至没有转移的语义. 只是一种类型的强制转换.
- std::vector<std::string> ve;
- std::string str="msg";
- ve.push_back(str);
- ve.push_back(std::move(str)); // 内部实现可能是这样子的
- void push_back(str)
- {
- T temp (str); // 调用值拷贝构造
- __insert(temp);
- }
- ve.push_back(std::move(str));
- {
- T temp (t) ; // 调用右值拷贝构造
- __insert(temp);
- }
确实会比 ve.push_back(str)快一点点, std::string 的右值拷贝构造直接转移了内存.
最终看起来像是 move 的功劳, 也实现了转移的语义.
但实际上是 std::string 的右值拷贝构造直接转移了内存. 当然感谢 move, 但 str 真的从左值变成了右值引用.
the end
来源: https://www.cnblogs.com/xuhuajie/p/11491924.html