移动赋值运算符和`if(this!=& rhs)` [英] Move assignment operator and `if (this != &rhs)`

查看:189
本文介绍了移动赋值运算符和`if(this!=& rhs)`的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在类的赋值运算符中,你通常需要检查被赋值的对象是否是调用对象,所以你不会搞错:

In the assignment operator of a class, you usually need to check if the object being assigned is the invoking object so you don't screw things up:

Class& Class::operator=(const Class& rhs) {
    if (this != &rhs) {
        // do the assignment
    }

    return *this;
}

你需要移动赋值操作符是否有这样的情况, this ==& rhs 是真的?

Do you need the same thing for the move assignment operator? Is there ever a situation where this == &rhs would be true?

? Class::operator=(Class&& rhs) {
    ?
}


推荐答案

很多在这里清理...

Wow, there is just so much to clean up here...

首先,复制和交换并不总是实现复制分配的正确方法。几乎肯定在 dumb_array 的情况下,这是一个次优解。

First, the Copy and Swap is not always the correct way to implement Copy Assignment. Almost certainly in the case of dumb_array, this is a sub-optimal solution.

使用复制和交换用于 dumb_array 是在底层使用最丰富的功能的最昂贵的操作的典型示例。它是完美的客户谁想要最全面的功能,并愿意支付性能惩罚。

The use of Copy and Swap is for dumb_array is a classic example of putting the most expensive operation with the fullest features at the bottom layer. It is perfect for clients who want the fullest feature and are willing to pay the performance penalty. They get exactly what they want.

但是对于那些不需要最全面的功能,而是寻求最高性能的客户来说,这是灾难性的。对于他们 dumb_array 只是另一个软件,他们必须重写,因为它太慢。 dumb_array 的设计不同,它可以满足两个客户,而不会影响任何一个客户。

But it is disastrous for clients who do not need the fullest feature and are instead looking for the highest performance. For them dumb_array is just another piece of software they have to rewrite because it is too slow. Had dumb_array been designed differently, it could have satisfied both clients with no compromises to either client.

满足两个客户端是在最低级别构建最快的操作,然后在更高的功能更昂贵的基础上添加API。也就是说你需要强大的异常保证,罚款,你支付它。你不需要它?这是一个更快的解决方案。

The key to satisfying both clients is to build the fastest operations in at the lowest level, and then to add API on top of that for fuller features at more expense. I.e. you need the strong exception guarantee, fine, you pay for it. You don't need it? Here's a faster solution.

让我们具体的:下面是快速的基本异常保证 dumb_array

Let's get concrete: Here's the fast, basic exception guarantee Copy Assignment operator for dumb_array:

dumb_array& operator=(const dumb_array& other)
{
    if (this != &other)
    {
        if (mSize != other.mSize)
        {
            delete [] mArray;
            mArray = nullptr;
            mArray = other.mSize ? new int[other.mSize] : nullptr;
            mSize = other.mSize;
        }
        std::copy(other.mArray, other.mArray + mSize, mArray);
    }
    return *this;
}

说明:

在现代硬件上你可以做的更昂贵的事情之一是去堆。任何你可以做的,以避免旅行到堆是时间&努力花费。 dumb_array 的客户端可能希望经常分配相同大小的数组。当他们这样做,你需要做的是一个 memcpy (隐藏在 std :: copy )。您不想分配相同大小的新数组,然后重新分配大小相同的旧数组!

One of the more expensive things you can do on modern hardware is make a trip to the heap. Anything you can do to avoid a trip to the heap is time & effort well spent. Clients of dumb_array may well want to often assign arrays of the same size. And when they do, all you need to do is a memcpy (hidden under std::copy). You don't want to allocate a new array of the same size and then deallocate the old one of the same size!

现在为您的客户谁想要强大的异常安全:

Now for your clients who actually want strong exception safety:

template <class C>
C&
strong_assign(C& lhs, C rhs)
{
    swap(lhs, rhs);
    return lhs;
}

或者如果你想利用C ++ 11中的移动赋值应该是:

Or maybe if you want to take advantage of move assignment in C++11 that should be:

template <class C>
C&
strong_assign(C& lhs, C rhs)
{
    lhs = std::move(rhs);
    return lhs;
}

如果 dumb_array s客户端的速度,他们应该调用 operator = 。如果他们需要强大的异常安全,有一些通用的算法,他们可以调用它将工作在各种各样的对象,只需要实现一次。

If dumb_array's clients value speed, they should call the operator=. If they need strong exception safety, there are generic algorithms they can call that will work on a wide variety of objects and need only be implemented once.

现在回到原来问题(在此时间点具有类型o):

Now back to the original question (which has a type-o at this point in time):

Class&
Class::operator=(Class&& rhs)
{
    if (this == &rhs)  // is this check needed?
    {
       // ...
    }
    return *this;
}

这实际上是一个有争议的问题。有些人会说肯定,有些人会说不。

This is actually a controversial question. Some will say yes, absolutely, some will say no.

我个人认为没有,你不需要这个支票。

My personal opinion is no, you don't need this check.

原理:

当对象绑定到右值引用时,它是以下两种情况之一:

When an object binds to an rvalue reference it is one of two things:


  1. 暂时。

  2. 呼叫者希望您相信的对象是临时对象。

如果你有一个实际临时对象的引用,那么根据定义,你有一个对该对象的唯一引用。它不可能被整个程序中的任何其他地方引用。也就是说 this ==& temporary 不可能

If you have a reference to an object that is an actual temporary, then by definition, you have a unique reference to that object. It can't possibly be referenced by anywhere else in your entire program. I.e. this == &temporary is not possible.

骗了你,并答应你,当你不是一个暂时的,那么是客户的责任,以确保你不必关心。如果你想真的小心,我相信这将是一个更好的实现:

Now if your client has lied to you and promised you that you're getting a temporary when you're not, then it is the client's responsibility to be sure that you don't have to care. If you want to be really careful, I believe that this would be a better implementation:

Class&
Class::operator=(Class&& other)
{
    assert(this != &other);
    // ...
    return *this;
}

如果您 传递了自我引用,则这是客户端应该修复的错误。

I.e. If you are passed a self reference, this is a bug on the part of the client that should be fixed.

为了完整起见, dumb_array 的移动赋值运算符:

For completeness, here is a move assignment operator for dumb_array:

dumb_array& operator=(dumb_array&& other)
{
    assert(this != &other);
    delete [] mArray;
    mSize = other.mSize;
    mArray = other.mArray;
    other.mSize = 0;
    other.mArray = nullptr;
    return *this;
}



在移动分配的典型用例中, *这个将是一个移动对象,因此 delete [] mArray; 应该是无操作。实现对nullptr尽可能快地删除是非常重要的。

In the typical use case of move assignment, *this will be a moved-from object and so delete [] mArray; should be a no-op. It is critical that implementations make delete on a nullptr as fast as possible.

注意:

swap(x,x)是一个好主意,或只是一个必要的邪恶。

Some will argue that swap(x, x) is a good idea, or just a necessary evil. And this, if the swap goes to the default swap, can cause a self-move-assignment.

我不同意交换(x,x) )一个好主意。如果发现在我自己的代码,我会考虑它的性能错误,并解决它。但是,如果你想允许它,意识到 swap(x,x)只对自动移动assignemnet移动的值。在我们的 dumb_array 示例中,如果我们简单地省略assert,或者约束到移动的情况,这将是完全无害的:

I disagree that swap(x, x) is ever a good idea. If found in my own code, I will consider it a performance bug and fix it. But in case you want to allow it, realize that swap(x, x) only does self-move-assignemnet on a moved-from value. And in our dumb_array example this will be perfectly harmless if we simply omit the assert, or constrain it to the moved-from case:

dumb_array& operator=(dumb_array&& other)
{
    assert(this != &other || mSize == 0);
    delete [] mArray;
    mSize = other.mSize;
    mArray = other.mArray;
    other.mSize = 0;
    other.mArray = nullptr;
    return *this;
}

如果您自行分配两个移出c> dumb_array ,除了在程序中插入无用的指令之外,你不会做任何不正确的事情。对于绝大多数对象也可以这样做。

If you self-assign two moved-from (empty) dumb_array's, you don't do anything incorrect aside from inserting useless instructions into your program. This same observation can be made for the vast majority of objects.

更新>

我已经给了这个问题一些想法,并改变了我的位置。我现在相信作业应该容忍自我分配,但是拷贝分配和移动分配的后置条件是不同的:

I've given this issue some more thought, and changed my position somewhat. I now believe that assignment should be tolerant of self assignment, but that the post conditions on copy assignment and move assignment are different:

对于拷贝分配:

x = y;

应该有一个后置条件, y 不应该改变。当& x ==& y 时,这个后置条件转换为:自我复制赋值应该不会影响 x

one should have a post-condition that the value of y should not be altered. When &x == &y then this postcondition translates into: self copy assignment should have no impact on the value of x.

移动作业:

x = std::move(y);

一个应该有一个后置条件 y 有一个有效但未指定的状态。当& x ==& y 时,此后置条件转换为: x 具有有效但未指定的状态。也就是说自我移动分配不一定是无操作。但它不应该崩溃。此后置条件与允许 swap(x,x)正常工作一致:

one should have a post-condition that y has a valid but unspecified state. When &x == &y then this postcondition translates into: x has a valid but unspecified state. I.e. self move assignment does not have to be a no-op. But it should not crash. This post-condition is consistent with allowing swap(x, x) to just work:

template <class T>
void
swap(T& x, T& y)
{
    // assume &x == &y
    T tmp(std::move(x));
    // x and y now have a valid but unspecified state
    x = std::move(y);
    // x and y still have a valid but unspecified state
    y = std::move(tmp);
    // x and y have the value of tmp, which is the value they had on entry
}

上面的工作,只要 x = std :: move(x)不会崩溃。它可以在任何有效但未指定的状态中留下 x

The above works, as long as x = std::move(x) doesn't crash. It can leave x in any valid but unspecified state.

我看到三种方法来编写移动赋值运算符 dumb_array 可实现此功能:

I see three ways to program the move assignment operator for dumb_array to achieve this:

dumb_array& operator=(dumb_array&& other)
{
    delete [] mArray;
    // set *this to a valid state before continuing
    mSize = 0;
    mArray = nullptr;
    // *this is now in a valid state, continue with move assignment
    mSize = other.mSize;
    mArray = other.mArray;
    other.mSize = 0;
    other.mArray = nullptr;
    return *this;
}

上述实现允许自指派,但 * this 其他在自动移动分配后最终为零大小的数组, *这是。这很好。

The above implementation tolerates self assignment, but *this and other end up being a zero-sized array after the self-move assignment, no matter what the original value of *this is. This is fine.

dumb_array& operator=(dumb_array&& other)
{
    if (this != &other)
    {
        delete [] mArray;
        mSize = other.mSize;
        mArray = other.mArray;
        other.mSize = 0;
        other.mArray = nullptr;
    }
    return *this;
}

上面的实现允许自我分配与拷贝赋值操作符相同,使它成为无操作。这也很好。

The above implementation tolerates self assignment the same way the copy assignment operator does, by making it a no-op. This is also fine.

dumb_array& operator=(dumb_array&& other)
{
    swap(other);
    return *this;
}

只有 dumb_array 不保存应该被立即销毁的资源。例如,如果唯一的资源是内存,上面是很好。如果 dumb_array 可能持有互斥锁或文件的打开状态,客户端可以合理地期望移动分配的lhs上的那些资源被立即释放,因此该实现可以有问题。

The above is ok only if dumb_array does not hold resources that should be destructed "immediately". For example if the only resource is memory, the above is fine. If dumb_array could possibly hold mutex locks or the open state of files, the client could reasonably expect those resources on the lhs of the move assignment to be immediately released and therefore this implementation could be problematic.

第一个的成本是两个额外的商店。第二个的成本是一个测试和分支。两个工作。两者都满足表22中的MoveAssignable要求在C ++ 11标准中的所有要求。第三个也按照非内存资源关注的模式工作。

The cost of the first is two extra stores. The cost of the second is a test-and-branch. Both work. Both meet all of the requirements of Table 22 MoveAssignable requirements in the C++11 standard. The third also works modulo the non-memory-resource-concern.

根据硬件,这三种实现可能有不同的成本:分支有多昂贵?是否有很多寄存器或非常少的寄存器?

All three implementations can have different costs depending on the hardware: How expensive is a branch? Are there lots of registers or very few?

外接程序是自动移动分配,不像自我拷贝分配,不必保留当前

The take-away is that self-move-assignment, unlike self-copy-assignment, does not have to preserve the current value.

< / Update >

</Update>

最终(希望)编辑受到Luc Danton评论的启发:

One final (hopefully) edit inspired by Luc Danton's comment:

写一个不直接管理内存的高级类(但可能有基础或成员),那么最好的移动赋值实现是:

If you're writing a high level class that doesn't directly manage memory (but may have bases or members that do), then the best implementation of move assignment is often:

Class& operator=(Class&&) = default;

这将依次移动分配每个基数和每个成员,不包括 this!=& other 检查。这将给你最高的性能和基本的异常安全性,假设你的基地和成员之间不需要维护不变量。对于要求强烈异常安全的客户,请指向 strong_assign

This will move assign each base and each member in turn, and will not include a this != &other check. This will give you the very highest performance and basic exception safety assuming no invariants need to be maintained among your bases and members. For your clients demanding strong exception safety, point them towards strong_assign.

这篇关于移动赋值运算符和`if(this!=&amp; rhs)`的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆