C++ Primer 5th笔记(chap 13 拷贝控制)移动构造和移动赋值

1. 移动构造函数和移动赋值运算符

一般来说,拷贝一个资源会导致一些额外的开销。在这种拷贝并非必要的情况下,定义了移动构造函数和移动赋值运算符的类就可以避免此问题。
eg.

StrVec::StrVec(StrVec &&s) noexcept //移动操作不应该抛出任何异常elements(s.elemenrs),first_free(s.first_free),cap(s.cap)
{
    
    
	//切断源对象与被移动资源的联系
	s.elements = s.first_free = nullptr;
}

StrVec & strVec::operator=(StrVec &&rhs) noexcept{
    
    
    if(this != &rhs){
    
    
        free()://释放已有元素
        elements = rhs.elments;
        first_free = rhs.first_free;
        cap = rhs.cap;
        rhs.elements = rhs.first_free = rhs.cap = nullptr;
    }
    return *this;
}
StrVec v1,v2;
v1 = v2;//v2是左值,使用拷贝赋值运算符
StrVec getVec(istream &);
v2 = getVec(cin);//getVec(cin)是一个右值;使用移动赋值运算符

eg2.

class HasPtr{
    
    
public:
    HasPtr(HasPtr &&p) noexcept:ps(p.ps),i(p.i){
    
    p.ps = 0}

    HasPtr& operator=(HasPtr rhs){
    
    
        swap(*this,rhs);
        return *this;
    }
};

hp = hp2;//hp2 是一个左值;hp2 通过拷贝构造函数来拷贝
hp = std::move(hp2);//移动构造函数来移动hp2

1.1 异常相关

  • noexcept通知标准库不抛出异常可以节省一些额外的操作。
  • vector 在调用 push_back 可能会要求为 vector 重新分配内存,重新分配内存的过程可能会发生异常,因此push_back一般调用拷贝构造函数。 除非 vector 知道移动构造函数不会发生异常。

1.2 移后源对象的状态

从一个对象移动数据并不会销毁对象,但有时希望移动操作之后源对象被销毁。当编写一个移动操作后,源对象的状态为:

  • 进入一个可析构的安全状态(对象中留下的值是个未知数)
  • 对象仍然有效(可以安全地为其赋予新值或可以安全地使用而不依赖当前的值)

2. 更新三五法则

如果一个类定义了任何一个拷贝操作,它就应该定义所有的五个操作。如前所述,某些类必须定义拷贝构造函数、拷贝赋值运算符和析构函数才能正确工作。这些类通常拥有一个资源,而拷贝成员必须拷贝此资源。

3. 删除函数

移到操作一般不会隐式地被编译器定义为删除函数,除非以下情形:

  • 与拷贝构造函数不同,移动构造函数被定义为删除函数的条件是:有类成员定义了自己的拷贝构造函数且未定义移动构造函数,或者是有类成员未定义自己的拷贝构造函数并且编译器也不能为其合成移动构造函数。移动赋值运算符的情况类似。
  • 如果有类成员的移动构造函数或移动赋值运算符被定义为删除的或者是不可访问的,则类的移动构造函数或移动赋值运算符被定义为删除的。
  • 类似拷贝构造函数,如果类的析构函数被定义为删除的或不可访问的,则类的移动构造函数被定义为删除的。
  • 类似拷贝赋值运算符,如果类的成员是 const 或是引用类型,则类的移动赋值运算符被定义为删除的。

eg.

扫描二维码关注公众号,回复: 12667955 查看本文章
//假定Y是一个类,他定义了自己的拷贝构造函数,但未定义自己的移动构造函数
struct hasY{
    
    
    hasY() = default;
    hasY(hasY &&) = default;
    Y mem;//hasY将有一个删除的移动构造函数
};

hasY hy,hy2 = std::move(hy);//错误,移动构造函数是删除的

4. 移动迭代器

make_move_iterator 将一个普通迭代器转化为一个移到迭代器。

void StrVec::reallocate()
{
    
    
    // we'll allocate space for twice as many elements as the current size
    auto newcapacity = size() ? 2 * size() : 1;

  // allocate new memory
	auto newdata = alloc.allocate(newcapacity);

 // move the data from the old memory to the new
	auto dest= uninitialized_copy(make_move_iterator(begin()),
	make_move_iterator(end()),newdata);
/*
	auto dest = newdata;  // points to the next free position in the new array
       auto elem = elements; // points to the next element in the old array
	for (size_t i = 0; i != size(); ++i)
		alloc.construct(dest++, std::move(*elem++));
*/
    free();  // free the old space once we've moved the elements

    // update our data structure to point to the new elements
    elements = newdata;
    first_free = dest;
    cap = elements + newcapacity;
}

猜你喜欢

转载自blog.csdn.net/thefist11cc/article/details/113876119