operator= 在 return *this 之后改变的 C++ 结果
C++ result of operator= changing after return *this
在这里,我有一个非常简单的程序,可以将一个值从一个对象移动到另一个对象,确保从它所取自的对象中删除该值(留下“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'
呃哦!不知何故,两个对象都得到了 "deleted"。当我单步执行移动赋值运算符代码的主体时……一切似乎都很好! a.m_val 就像我们期望的那样 'b'...直到 return *this;
语句。一旦它从函数中 returns ,该值突然被设置回“0”。
谁能解释一下为什么会这样?
P<T>& operator=(P<T>&& other) noexcept
这是此模板的显式移动赋值运算符 class。
struct S : public P<S> {
此子class 继承自此模板class。 P<S>
是它的父 class.
这个 subclass 没有显式的移动赋值运算符,因此您的 C++ 编译器会帮助您创建一个默认的移动赋值运算符,因为这就是 C++ 的工作方式。默认移动赋值运算符调用父 class 的移动赋值运算符,然后默认移动赋值运算符移动赋值此 class.
的所有成员
仅仅因为父 class 有一个明确的移动赋值运算符(你的移动赋值运算符)不会使 this child class 的默认移动赋值运算符消失。 S
的默认移动赋值运算符实际上是这样的,非常 松散地说:
S &operator=(S &&other)
{
P<S>::operator=(std::move(other));
this->m_val=std::move(other.m_val);
return *this;
}
这就是您从 C++ 编译器免费获得的。您的 C++ 编译器为您的 class 提供如此有用的默认移动赋值运算符不是很好吗?
a = std::move(b);
这实际上最终调用了上面的默认移动赋值运算符。
它首先调用父 class 的移动赋值运算符,即您编写的那个。
这有效地将 other.m_val
设置为 '0'
。
并且当它 returns 时,此默认移动赋值运算符还将 this->m_val
设置为 '0'
。
问题是S
有一个implicitly generated move assignment operator,它调用基class(即P<T>::operator=
)的移动赋值运算符,然后执行member-对成员进行明智的移动分配(即 S::m_val
)。在P<T>::operator=
中,other.m_val
已经赋值给'0'
,然后回到S::operator=
,this->m_val
被other.m_val
赋值,变成了'0'
也是
您可以为 S
定义一个用户定义的移动赋值运算符,并且不期望调用基础 class 版本。例如
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;
}
};
在这里,我有一个非常简单的程序,可以将一个值从一个对象移动到另一个对象,确保从它所取自的对象中删除该值(留下“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'
呃哦!不知何故,两个对象都得到了 "deleted"。当我单步执行移动赋值运算符代码的主体时……一切似乎都很好! a.m_val 就像我们期望的那样 'b'...直到 return *this;
语句。一旦它从函数中 returns ,该值突然被设置回“0”。
谁能解释一下为什么会这样?
P<T>& operator=(P<T>&& other) noexcept
这是此模板的显式移动赋值运算符 class。
struct S : public P<S> {
此子class 继承自此模板class。 P<S>
是它的父 class.
这个 subclass 没有显式的移动赋值运算符,因此您的 C++ 编译器会帮助您创建一个默认的移动赋值运算符,因为这就是 C++ 的工作方式。默认移动赋值运算符调用父 class 的移动赋值运算符,然后默认移动赋值运算符移动赋值此 class.
的所有成员仅仅因为父 class 有一个明确的移动赋值运算符(你的移动赋值运算符)不会使 this child class 的默认移动赋值运算符消失。 S
的默认移动赋值运算符实际上是这样的,非常 松散地说:
S &operator=(S &&other)
{
P<S>::operator=(std::move(other));
this->m_val=std::move(other.m_val);
return *this;
}
这就是您从 C++ 编译器免费获得的。您的 C++ 编译器为您的 class 提供如此有用的默认移动赋值运算符不是很好吗?
a = std::move(b);
这实际上最终调用了上面的默认移动赋值运算符。
它首先调用父 class 的移动赋值运算符,即您编写的那个。
这有效地将 other.m_val
设置为 '0'
。
并且当它 returns 时,此默认移动赋值运算符还将 this->m_val
设置为 '0'
。
问题是S
有一个implicitly generated move assignment operator,它调用基class(即P<T>::operator=
)的移动赋值运算符,然后执行member-对成员进行明智的移动分配(即 S::m_val
)。在P<T>::operator=
中,other.m_val
已经赋值给'0'
,然后回到S::operator=
,this->m_val
被other.m_val
赋值,变成了'0'
也是
您可以为 S
定义一个用户定义的移动赋值运算符,并且不期望调用基础 class 版本。例如
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;
}
};