C++的一个最重要的特性之一就是支持 move semantic(搬迁语义/移动语义),这项语义更加贴合C++的主要设计目标:用以避免非必要拷贝(copy)和临时对象(temporary)。
首先,我们要知道为什么需要move语义,对于以下代码:
对于上述代码,我们将新对象插入集合中,后者提供一个成员函数可为传入的元素建立一份内部拷贝(internal copy) ,set.insert()简化模板可为:
这样的建立拷贝的方式是有用的,因为对于创建的对象x在被插入集合后,这个对象仍有可能被使用或改动。
对于后可两次安插动作来说,更好的指出“被传入值不再被调用者使用或修改”,如此一来coll内部就无须为它建立一份copy且可以“以某种方式move其内容进入新建元素中”。当x的复制成本高昂时--例如是一个巨大的string集合-这会带来巨大的效能提高。
自C++11起,这种行为成为可能,然而程序员必须自行指明“move是可行的,除非被安插的对象还会被使用或修改”,编译器自身允许程序员执行者这项工作可使这个特性用于逻辑上任何适当之处。只须修改先前的代码为:
有了声明于
对于std::move()自身并不做任何的moveing工作,它只是将其实参转成一个所谓的
rvalue reference(右值引用),右值引用是一种声明为X&&的类型。这种新类型主张rvalue可被改动内容(rvalue:不具名的临时对象只能出现于赋值操作的右侧。)这份契约的含义是:这是个不再被需要的(临时)对象,所以你可以“偷”其内容或其资源。
我们可以让集合提供一个insert()重载版本,用以处理这些rvalue reference:
我们可以优化这个rvalue reference的重载版本,令它”偷取“x的内容。为了这么做,需要type of x的帮助,因为只有type of x拥有接近本质的机会和权力。你可以运用内置数组(internal array)和x类型的指针(pointer of x)来初始化被安插的元素。
如果class x本身是一个复杂类型,原本你必须为它逐一复制元素,现在这么做则会带来巨大的效能改善。欲初始化新的内部元素,我们只需调用class x提供的一个所谓的move构造函数,它”偷取“传入的实参值,初始化一个新元素。所有的复杂类型都应当提供一个这样的特殊的构造函数--用来将一个既有元素的内容搬迁到新元素中。
例如对于类XC++11提供了这样的功能:
例如 对于string的move构造函数来说,move构造函数只是将既有的内部的字符数组赋予新对象,而非建立一个新的数组复制所有元素。同样的情况也适用于所有的class:不再为所有元素建立一份拷贝,只需将内存赋予新对象即可。如果move构造函数不存在,copy构造函数就会被使用。
此外,你必须确保对于被传对象(被传对象:其value被”偷取“)的任何改动——特别是析构—都不至于对新对象的状态产生影响。因此,你往往必须清除被传入的实参的内容(如上例中rvalue),例如将nullptr赋值给”指向了容器内元素“的成员函数。
一般而言,C++标准库的class保证了,在一次move之后,对象处于有效但不确定的状态。也就是说,你可以在move之后对它赋予新值,但当前值是不确定的。STL容器则保证了,被move内容者,move后其值为空。
同样的,任何一个重要的类都应该同时提供一个copy assignment(拷贝赋值)和一个
move assignment(移动赋值)操作符。
对于 string 和集合,上述操作符可以进行简单的交换内部内容和资源就好,然而你也应该清楚*this指向的内容,因为这个对象可能持有资源(例如 lock),所以因该尽早释放他们,虽然 move语义并不强求你这样做。
Rvalue和Lvalue Reference的重载规则
对于
lvalue(左值):可操作对象,内存中具有明确的地址。
rvalue (右值):不可操作对象,内存中没有具体地址。
如果你只是实现
void foo(X&);
而没有实现 void foo(X&&) 则可因 lvalue 但不能因 rvalue 被调用。
如果只是实现
voif foo(const X&);
而没有实现 void foo(X&&) 则可因 lvalue 也可因 rvalue 被调用。
如果实现 void foo(X&),void foo(X&&)
或
实现 voif foo(const X&),void foo(X&&)
你可以区分”为 rvalue 服务“和”为 lvalue 服务“。且“为lvalue服务”的版本被允许且应该提供move语义。
如果只实现 void foo(X&&)
但没有实现 voif foo(const X&), void foo(X&)。foo()可因rvalue被调用但当使用lvalue调用时,会触发编译器错误。
以上这些意味着,如果 class 未提供 move 语义,只提供惯常的 copy 构造函数和
copy assignemnt 操作符,rvalue reference可以调用他们,因此 std::move()意味着“如果提供move语义则调用它,否则调用copy语义。
返回Rvalue Reference
对于move()你不需要也不应该要求move()返回值。
对于以下代码:
保证有以下行为:
保证有下列行为:
·如果X有一个可取用的copy或move构造函数,编译器可以选择略去其中的copy版本。
这也就是所谓的返回最优解。
否则,如果x有一个move构造函数,X就被 moved。
否则,如果x有一个copy构造函数,X就被 copied(复制)。
否则,报出一个编译期错误(compile-time error)。
也请注意,如果返回的是个local nonstatic 对象,那么返回其rvalue reference是不对的:
例如:
是的,rvalue reference 也是个reference,如果返回它而它指向(referring to)local 对象,意味着你返回一个reference却指向一个不再存在的对象。是否对它使用std::move()倒是无关紧要。