Coding 极简派

偷梁换柱 - copy 和 move

你怎么交换两个变量值?

这大概是每个初学编程的人都会遇到的一个问题. 你的第一反应一定是

1
2
3
4
5
6
7
template<class T>
void swap(T& a, T& b)
{
T tmp(a);
a = b;
b = tmp;
}

简单来说就是当你需要交换两杯水的时候,你需要借助第三个杯子.

考虑一下它的中间开销.

  1. T tmp(a).你利用copy constructor重新构造了一个对象tmp,并且复制了a.
  2. 用 a复制了b
  3. 用b 复制了a

如果copy的代价很高的化…

然而有没有不复制的办法来交换两个变量的值?
有一个经典的告诫大家学会分享的萧伯纳名言:

“如果你有一个苹果,我有一个苹果,彼此交换,那么,每个人只有一个苹果;
如果你有一个思想,我有一个思想,彼此交换,我们每个人就有了两个思想,甚至多于两个思想。”

我们不是要说分享,是要说拷贝和移动的关系.如果人手一台3D打印机,可能每人也有两个苹果吧. 但你看到了,拷贝的过程,是要经历生产的开销. 即使是思想的拷贝,如果它足够复杂晦涩,那相比也要死不少脑细胞.

在C++中,有一个还比较古老的操作符叫move,就起到了这种移动的作用.
下面的一段引用来说明copy和move的关系.

Hitherto, copying has been the only means for transferring a state from one object to another (an object’s state is the collective set of its non-static data members’ values). Formally, copying causes a target object t to end up with the same state as the source s, without modifying s. For example, when you copy a string s1 to s2, the result is two identical strings with the same state as s1.
Notwithstanding the conceptual difference between copying and moving, there’s a practical difference too: Move operations tend to be faster than copying because they transfer an existing resource to a new destination, whereas copying requires the creation of a new resource from scratch. The efficiency of moving can be witnessed among the rest in functions that return objects by value.

所以交换两个值可以写作

1
2
3
4
5
6
7
template<class T>
void swap(T&a, T& b)
{
T tmp = move(a);//此时a时效了,内部的数据被move到了tmp中)
a = move(b);
b = move(tmp);
}

好处是减少构造一些不必要的对象,节省了对象的空间消耗和construction 和destruction 的过程.

函数返还一个container

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
using namespace std;
vector<int> doubleValues (const vector<int>& v)
{
vector<int> new_values;
new_values.reserve(v.size());
for (auto itr = v.begin(), end_itr = v.end(); itr != end_itr; ++itr )
{
new_values.push_back( 2 * *itr );
}
return new_values;
}
int main()
{
vector<int> v;
for ( int i = 0; i < 100; i++ )
{
v.push_back( i );
}
v = doubleValues( v );
}

这是又一个例子.
这里new_values 除了在doubleValues() 里面构造了一个,在你把函数返回按结果作为右值赋给v的时候又复制了一遍.之前的new_values 作为function 的local value自然会之后被回收. 但如果直接可以把它移动出来减少拷贝开销该有多好.
move() comes to rescue again!

这里就要提到左值和右值的区别.
左值可以被看作一个有名字的value/object, 而右值是一个无名的临时value.

1
x+(y*z); // A C++ expression that produces a temporary

这个临时构造的value在你到达分号的时候就灰飞烟灭了.
那怎么做到移动而不拷贝呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
int x;
int getInt ()
{
return x;
}
int && getRvalueInt ()
{
// notice that it's fine to move a primitive type--remember, std::move is just a cast
return std::move( x );
}
xubing wechat
奇闻共欣赏,疑义相与析.欢迎来我的微信公众号