【问题标题】:C++ result of operator= changing after return *this运算符 = 的 C++ 结果在返回 *this 后发生变化
【发布时间】:2020-06-12 03:25:57
【问题描述】:

在这里,我有一个非常简单的程序,可以将值从一个对象移动到另一个对象,确保从获取它的对象中删除该值(留下“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”。 谁能解释一下为什么会这样?

【问题讨论】:

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


    【解决方案1】:
    P<T>& operator=(P<T>&& other) noexcept
    

    这是此模板类的显式移动赋值运算符。

    struct S : public P<S> {
    

    这个子类继承自这个模板类。 P&lt;S&gt; 是它的父类。

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

    仅仅因为父类有一个显式的移动赋值运算符(你的移动赋值运算符)不会使 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-&gt;m_val设置为'0'

    【讨论】:

    • 啊天哪...我不敢相信我错过了!感谢您的帮助!
    【解决方案2】:

    问题是S有一个implicitly generated move assignment operator,它调用基类的移动赋值操作符(即P&lt;T&gt;::operator=),然后对成员执行member-wise move assignment(即S::m_val) .在P&lt;T&gt;::operator=中,other.m_val已经被分配给'0',然后又回到S::operator=this-&gt;m_valother.m_val分配,也变成了'0'

    您可以为S 定义一个用户定义的移动赋值运算符,并且不需要调用基类版本。例如

    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

    【讨论】:

    • 感谢大家的帮助!这一点,下面 Sam Varshavchik 的回答为我回答了这个问题。 :)
    猜你喜欢
    • 2020-05-12
    • 1970-01-01
    • 2011-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-12
    • 1970-01-01
    • 2019-03-14
    相关资源
    最近更新 更多