I learned something today: C++ (part II)
Move semantics
最近在读一本名叫“C++ High Performance”的书,觉得有一定帮助,学习了一点现代的C++,故记录下自己通俗的理解。
Rule of three
前人的经验告诉我们,一个类应该负责好自己所拥有的资源的管理,当一个类被复制(copy),被赋值(assign)到其他类或是被析构(destruct)的时候,它应对所拥有的资源也应该做出相应的正确的操作。这一理念在实践中被提炼为 the rule of three。“three” 代表一个类的 copy-constructor,copy-assignment 和 destructor 三个成员函数,如果你自定义了这三个函数中的其中一个,那么你很有可能还需明确定义其他两个函数。如下面Buffer类
1 | class Buffer { |
然而,这样有很多复制的操作,复制是有代价的,有时我们并不想要复制,传入的对象也不需要被保留,比如当我们把一个函数的返回值直接作为函数参数时。使用指针可以避免一些复制,但管理指针也并不那么方便:只能靠程序员自己。
Rule of five and Move semantics
于是,在the rule of three 的基础上,有了 the rule of five 。为了解决复制带来的问题,我们定义两个全新的函数:move constructor 和move assignment operator 。从名字就可以猜到,这两个函数不复制,而是直接移动资源。一个类定义了自己的move constructor后,就可以实现move semantics。
下面举一个利用move semantics带来好处的例子:假设我们要把若干个前面的buffer类,放入std::vector
里。当std::vector
容量不够的时候,必须重新分配更多的内存,然后利用拷贝构造函数,把原来内存里的旧对象一个个复制到新内存中,然后再摧毁旧的对象。但是如果我们的buffer类定义了move constructor ,std::vector
就可以直接把旧对象移过来。
那么事不宜迟,我们马上为buffer类加上move constructor 和move assignment operator :
1 | Buffer(Buffer&& other) noexcept //不使用noexcept将不便于STL使用move semantics |
可以看到,我们把拷贝构造函数里的 Buffer&
改成了 Buffer&&
:有或是没有&&
,就代表了我们是想要“move”还是“copy”。&&
这个东西用C++术语来说,叫做Rvalue Reference Declarator ,表示这两个函数接收所谓的r-value , 当我们调用构造函数,传入的是r-value时,我们便运用的是“move”而不是“copy”。
那么什么是r-value?粗要的理解,r-value就是我们没给名字的对象。例如我们直接用函数的返回值做参数。同时也可以用 std::move()
得到r-value。但是注意,int,float等等原始的数据类型只会被拷贝。见如下代码:
1 | using namespace std; |