C ++运算符的结果=返回后更改* this

时间:2020-02-28 03:08:11

标签: c++ templates variable-assignment operator-keyword stdmove

在这里,我有一个非常简单的程序,可将值从一个对象移动到另一个对象,并确保将其值从获取该对象的对象中删除(留一个“ 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;
}

按预期,该程序的输出为:

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

'b'的值从对象b转移到对象a,而后留一个'0'。 现在,如果我用一个模板来概括这一点,以(希望)自动进行移动和删除业务,这就是我最终得到的...(当然是摘录了)。

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

运行时,输出为:

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

哦,哦!不知何故,两个对象都被“删除”了。当我逐步完成移动分配运算符代码的正文时,一切似乎都很好!就像我们期望的那样,a.m_val为'b',直到return *this;语句为止。从函数返回后,该值突然设置回“ 0”。 有人可以说明为什么会这样吗?

2 个答案:

答案 0 :(得分:3)

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

这是此模板类的显式移动分配运算符。

struct S : public P<S> {

此子类从此模板类继承。 P<S>是其父类。

此子类没有显式的移动赋值运算符,因此C ++编译器会为您创建默认的移动赋值运算符,因为这就是C ++的工作方式。默认的move-assignment运算符调用父类的move赋值运算符,然后默认的move Assigns运算符对该类的所有成员进行move-assign。

仅因为父类具有显式的移动分配操作符(您的移动分配操作符),并不会使 this 子类的默认移动分配操作符消失。 S的默认移动分配运算符实际上是 非常 粗略地说:

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

    return *this;
}

这就是从C ++编译器免费获得的东西。您的C ++编译器为类提供如此有用的默认移动赋值运算符不是很好吗?

a = std::move(b);

实际上,这最终调用了上面的默认移动分配运算符。

首先调用父类的移动赋值运算符,即您编写的那个。

有效地将other.m_val设置为'0'

返回时,此默认移动分配运算符还将this->m_val设置为'0'

答案 1 :(得分:3)

问题是S有一个implicitly generated move assignment operator,它调用基类的移动分配运算符(即P<T>::operator=),然后在成员上执行逐成员的移动分配(即S::m_val)。在P<T>::operator=中,other.m_val已分配给'0',然后返回到S::operator= this->m_valother.m_val分配并成为{{1} }。

您可以为'0'定义用户定义的移动分配运算符,并且不希望调用基类版本。例如

S

LIVE