这篇根据一些文章整理,对移动语义进行详细记录
移动语义
const& 复制构造存在的问题
复制构造在前面的文章中有记录,它的主要一个问题在于使用const &进行常引用,导致被复制的对象不能修改。按照常理来说,一般不需要修改被复制的对象,但在某些情况下却非常有用。
首先看个代码:
Person make_person(){ auto person=Person(); return person; }
分析:
首先产生一个局部对象,由于返回的是一个对象,因此产生复制构造操作,而且要求复制构造函数必须为const &的形式,否则出现错误:找不到合适的复制构造函数。因此Person的定义至少像这种形式:
class Person{ public: Person(const Person &person){...} //const &复制构造 };
这种样式是没有问题的,但是考虑一种情况,如果Person拥有资源怎么办?假如它有一个char *分配了100个字节的堆,那么问题演变成下面的模型:
模型:已知A有资源R,现在希望能将R的所有权移给B,出发点在于避免重复多次的分配资源。
由于复制构造是常引用,我们可以做到把B.R = A.R,这样B拥有了A的资源,但是两者都指向同一块资源,在析构中势必会delete资源,那么B得到的资源将会无效!
因此思路可以转换为:如果有一个复制构造不仅可以赋值,而且可以修改被复制的对象,那么这个问题就解决了。
解决思路如下:B.R = A.R,然后将 A.R =nullptr。这样对象A即使析构使用delete也没有问题。在这种需求下,移动复制构造隆重出场!
移动复制构造
移动复制构造主要和右值有关系。右值主要包括临时对象和字面量的形式。在C++中有几种复制构造,因此编译器会选择最合适的重载。还是以上面的例子为例,在产生临时对象这一步,编译器选择的重载顺序如下:
移动复制构造 > const &复制构造
代码比较清楚的说明这一点,在临时对象采用哪种形式的构造上,编译器首选是移动复制构造,其中第四步的std::move下面记录下。
class Person { public: char *m_pSource; //资源 Person(const Person &person) { std::cout << "复制构造执行" << std::endl; } Person(Person &&person) { this->m_pSource = person.m_pSource; person.m_pSource = nullptr; std::cout << "移动复制构造执行" << std::endl; } Person() { m_pSource = new char[100]; printf("原始资源:%p\n", m_pSource); } ~Person() { delete[]m_pSource; //释放资源 std::cout << "析构执行" << std::endl; } }; Person test_Person() { Person person = Person(); return person; } int main() { Person p1 = test_Person(); Person p2(std::move(p1)); printf("p2资源:%p\n", p2.m_pSource); printf("p1资源:%p\n", p1.m_pSource); }
再论右值及std::move
std::move等价为对左值执行static_cast<T &&>操作,那么原对象将会变成临终值,临终值也是右值的一种形式。用处在于可以选择移动复制构造的重载。正如上面4标识的,std::move(p1)之后将会调用右值引用的重载。
如果不使用,则调用const &的重载,比如 Person p3(p2)。
经过一系列的move,原始资源从最初的局部对象person,最终辗转到p2,person -> p1 ->p2,printf说明了这一点
一道测试题
/* @一道测试题,如果能看懂代码为什么出问题,说明整个基础概念都了解了 @tinaluo 2021-02-15夜 */ class Person { public: char *m_pSource; //资源 Person(const Person &person) { std::cout << "复制构造执行" << std::endl; } Person(Person &&person) { this->m_pSource = person.m_pSource; person.m_pSource = nullptr; std::cout << "移动复制构造执行" << std::endl; } Person() { m_pSource = new char[100]; printf("原始资源:%p\n", m_pSource); } ~Person() { delete[]m_pSource; //释放资源 std::cout << "析构执行" << std::endl; } }; Person test_args(Person person) { printf("add %p\n", person.m_pSource); return person; } int main() { Person p1; printf("add %p\n", p1.m_pSource); test_args(p1); }