C++ Primer第十三章⑦

C++ Primer

拷贝控制

对象移动

现在我们在赋值语句中要分清使用的是移动构造函数还是拷贝构造函数了,老规矩,谁更匹配谁上:

StrVec v1, v2;
v1 = v2; //拷贝赋值
StrVec getVec(istream &); //函数声明,返回非引用,即返回右值
v2 = getVec(cin); //移动赋值

这是在StrVec类中移动操作和拷贝操作都有的情况下,如果是下面这种情况呢:

class Foo
{
public:
    Foo() = default; //强行合成默认构造函数
    Foo(const Foo&); //自定义拷贝构造函数(函数声明)
    //所以说,Foo没有移动构造函数
};
Foo x;
Foo y(x); //直接初始化,调用拷贝构造函数,因为x是左值
Foo z(std::move(x)); //还是调用拷贝构造函数,虽然我们把x当右值用,但人家没有移动操作啊

概括就是:如果一个类有拷贝构造函数而没有移动构造函数,那其对象的移动是通过拷贝来完成的,这一点对于拷贝赋值运算符和移动赋值运算符也适用。

接下来看一个很神奇的现象:

移动赋值运算符和拷贝赋值运算符可以是同一个函数

我们先抛代码:

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;
    }
};

我们来仔细看一下赋值运算符,它的参数是值传递的,就是说实参传过来的时候要拷贝给形参,那么来了,拷贝初始化要么是调用拷贝构造函数要么是移动构造函数,这取决于实参的类型,左值拷贝,右值移动,这样的话,这不就实现了单一的赋值运算符实现拷贝和移动赋值功能了嘛,来来来,举个例子:

HasPtr hp, hp2; //调用默认构造函数初始化
hp = hp2; //hp2作为变量是个左值,调用拷贝构造函数来赋值
hp = std::move(hp2); //强行右值,调用移动构造函数来移动hp2

在swap后,rhs中的指针指向原来的this指向的string,当rhs离开作用域后,这个string就被销毁了,perfect

移动操作的好处-举个例子

我们以之前的邮件类为例:通过定义移动操作,Message类可以使用string和set的移动操作来避免拷贝contents和folders成员的额外开销。

除了移动folders成员外,我们还要更新每个指向原Message的Folder-删除指向旧Message的指针,添加指向新Message的指针。

因为移动操作都需要更新Folder指针,我们把它封装成函数以便复用:

void Message::move_Folders(Message *m)
{
    folders = std::move(m->folders); //使用set的移动赋值运算符,避免了不必要的拷贝
    for(auto f : folders)
    {
        f->remMsg(m); //从Folder中删除旧Message
        f->addMsg(this); //将本Message添加到Folder中
    }
    m->folders.clear(); //确保销毁m是无害的
}

接下来就可以很方便地自定义移动操作了:

//移动构造函数
Message::Message(Message &&m) : contents(std::move(m.content))
{
    move_Folders(&m); //移动folders并更新Folder指针
}

//移动赋值运算符
Message& Message::operator=(Message &&rhs)
{
    if(this != &rhs) //检查自赋值
    {
        remove_from_Folders(); //销毁this指向的旧状态
        contents = std::move(rhs.contents); //调用move将rhs的content移动到this对象
        move_Folders(&rhs); //更新Folders指向本Message
    }
    return *this;
}
移动迭代器

我们通过标准库的make_move_iterator函数来将普通迭代器转换为移动迭代器:

void StrVEc::reallpcate()
{
    auto newcapacity = size() ? z*size() : 1;
    auto = first = alloc.allocate(newcapacity);
    auto last = uninitial_copy(make_move_iterator((begin()),
    make_move_iterator(end()), first);

    free(); //释放旧空间

    //更新指针
    elements = first;
    first_free = last;
    cap = elements + newcapacity;
}

uninitialized_copy对输入序列中每个元素调用construct来将元素“拷贝”到nudist位置,因为我们传给它的表示范围的迭代器是移动迭代器,那相应的解引用运算符生成的就是右值引用,所以construct会使用移动构造函数来构造元素。

不要随意使用移动操作,因为你不知道以后源对象是什么状态,而且你也不能再去对它做什么,当然如果你够自信,都是自己良好定义的,那还是鼓励用的。

#C++工程师#
全部评论

相关推荐

Edgestr:没项目地址就干脆把那一栏删了呗
点赞 评论 收藏
分享
评论
2
3
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务