简体   繁体   English

返回 *this 后 operator= 更改的 C++ 结果

[英]C++ result of operator= changing after return *this

Here, I have a very simple program that moves a value from one object to another, making sure to remove the value from the one that it was taken from (leaving behind a '0').在这里,我有一个非常简单的程序,它将一个值从一个对象移动到另一个对象,确保从它所取的对象中删除该值(留下一个“0”)。

#include <iostream>

struct S
{
    S(char val) : m_val(val) {}
    S& operator=(S&& other) noexcept
    {
        this->m_val = other.m_val;
        other.m_val = '0';

        return *this;
    }
    char m_val = '0';
};

int main()
{
    S a('a');
    S b('b');

    std::cout << "a.m_val = '" << a.m_val << "'" << std::endl;
    std::cout << "b.m_val = '" << b.m_val << "'" << std::endl;

    a = std::move(b);

    std::cout << "a.m_val = '" << a.m_val << "'" << std::endl;
    std::cout << "b.m_val = '" << b.m_val << "'" << std::endl;

    return 0;
}

As expected, the output of this program is:正如预期的那样,这个程序的输出是:

a.m_val = 'a'
b.m_val = 'b'
a.m_val = 'b'
b.m_val = '0'

The value of 'b' is transferred from object b to object a, leaving a '0' behind. 'b' 的值从对象 b 转移到对象 a,留下一个 '0'。 Now, if I generalize this a bit more with a template to (hopefully) automatically do the move and delete business, here's what I end up with... (distilled down of course).现在,如果我用一个模板(希望)自动进行移动和删除业务来概括这一点,这就是我最终得到的......(当然是精炼的)。

#include <iostream>

template<typename T>
struct P
{
    P<T>& operator=(P<T>&& other) noexcept
    {
        T& thisDerived = static_cast<T&>(*this);
        T& otherDerived = static_cast<T&>(other);

        thisDerived = otherDerived;
        otherDerived.m_val = '0';

        return *this;
    }
protected:
    P<T>& operator=(const P<T>& other) = default;
};

struct S : public P<S>
{
    S(char val) : m_val(val) {}

    char m_val = '0';
};

int main()
{
    S a('a');
    S b('b');

    std::cout << "a.m_val = '" << a.m_val << "'" << std::endl;
    std::cout << "b.m_val = '" << b.m_val << "'" << std::endl;

    a = std::move(b);

    std::cout << "a.m_val = '" << a.m_val << "'" << std::endl;
    std::cout << "b.m_val = '" << b.m_val << "'" << std::endl;

    return 0;
}

When run, the output is:运行时,输出为:

a.m_val = 'a'
b.m_val = 'b'
a.m_val = '0'
b.m_val = '0'

Uh oh!哦哦! Somehow BOTH objects got "deleted".不知何故,两个对象都被“删除”了。 When I step through the body of the move assignment operator code... all seems well!当我逐步完成移动赋值运算符代码的主体时……一切似乎都很好! a.m_val is 'b' like we expect... right up until the return *this; a.m_val 是 'b' 就像我们期望的那样......直到return *this; statement.陈述。 Once it returns from the function, suddenly that value gets set back to '0'.一旦它从函数返回,该值会突然被设置回“0”。 Can anybody please shed some light on why this is happening?有人可以解释一下为什么会发生这种情况吗?

P<T>& operator=(P<T>&& other) noexcept

This is an explicit move assignment operator for this template class.这是此模板类的显式移动赋值运算符。

struct S : public P<S> {

This subclass inherits from this template class.这个子类继承自这个模板类。 P<S> is its parent class. P<S>是它的父类。

This subclass does not have an explicit move assignment operator, so your C++ compiler helpfully creates a default move assignment operator for you, because that's how C++ works.这个子类没有显式的移动赋值运算符,所以你的 C++ 编译器会帮助你创建一个默认的移动赋值运算符,因为这就是 C++ 的工作方式。 The default move-assignment operator invokes the parent class's move assignment operator, and the default move assignment operator then move-assigns all members of this class.默认的移动赋值运算符调用父类的移动赋值运算符,然后默认的移动赋值运算符对此类的所有成员进行移动赋值。

Just because the parent class has an explicit move assignment operator (your move assignment operator) doesn't make this child class's default move assignment operator disappear.仅仅因为父类具有显式移动赋值运算符(您的移动赋值运算符)并不会使子类的默认移动赋值运算符消失。 S 's default move assignment operator is effectively this, very loosely speaking: S的默认移动赋值运算符实际上是这样的,非常松散地说:

S &operator=(S &&other)
{
    P<S>::operator=(std::move(other));
    this->m_val=std::move(other.m_val);

    return *this;
}

That's what you get for free, from your C++ compiler.这就是您从 C++ 编译器免费获得的。 Isn't it nice of your C++ compiler to provide such a useful default move assignment operator for your class?您的 C++ 编译器为您的类提供如此有用的默认移动赋值运算符不是很好吗?

a = std::move(b);

This actually ends up invoking the above default move assignment operator.这实际上最终会调用上面的默认移动赋值运算符。

Which first invokes the parent class's move assignment operator, the one you wrote.它首先调用父类的移动赋值运算符,即您编写的那个。

Which effectively sets other.m_val to '0' .这有效地将other.m_val设置为'0'

And when it returns, this default move assignment operator also sets this->m_val to '0' .当它返回时,这个默认的移动赋值运算符还将this->m_val'0'

The problem is that S has a implicitly generated move assignment operator , which calls the move assignment operator of the base class (ie the P<T>::operator= ), then perform member-wise move assignment on the members (ie S::m_val ).问题是S有一个隐式生成的移动赋值运算符,它调用基类的移动赋值运算符(即P<T>::operator= ),然后对成员执行成员移动赋值(即S::m_val )。 In the P<T>::operator= , other.m_val has been assigned to '0' , then back to S::operator= this->m_val is assigned by other.m_val and becomes '0' too.P<T>::operator=other.m_val已经被赋值为'0' ,然后回到S::operator= this->m_valother.m_val并且也变成了'0'

You can define a user-defined move assignment operator for S and does nothing expect calling the base class version.您可以为S定义一个用户定义的移动赋值运算符,并且除了调用基类版本之外什么也不做。 eg例如

struct S : public P<S>
{
    S(char val) : m_val(val) {}

    char m_val = '0';

    S& operator=(S&& other) {
        P<S>::operator=(other);
        return *this;
    }
};

LIVE居住

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM