【发布时间】:2019-03-27 11:50:14
【问题描述】:
我观察到一个奇怪的行为,我不太了解析构函数和(默认)复制和移动分配。
假设我有一个类B 具有默认所有内容,还有一个类Test 具有自定义析构函数、默认复制分配和(可能)默认移动分配。
然后我们创建一个B 的实例,将其分配给一个变量,并使用赋值替换为一个新实例(其中右侧是右值)。
我觉得有两件事很奇怪,我在文档中看不到它们的原因。
- 当
Test没有move assignment(因此调用它的复制赋值)时,T1对象的析构函数不会被显式调用。我认为在这种情况下,惯用的做法是将资源作为copy assignment的一部分进行清理。但是,当move assignment存在(并被调用)时,为什么会有所不同?如果它在那里Test的析构函数被显式调用(?由操作员)。 - 文档指定移动分配后的
other可以保持任何状态。如果 B 的成员没有move assignment,为什么不调用 T2 的时间右值(即=B("T2")的右侧)的析构函数?
游乐场代码:https://onlinegdb.com/S1lCYmkKOV
#include <iostream>
#include <string>
class Test
{
public:
std::string _name;
Test(std::string name) : _name(name) { }
~Test()
{
std::cout << "Destructor " << _name << std::endl;
}
Test& operator=(const Test& fellow) = default;
//Test & operator= ( Test && ) = default;
};
class B {
public:
Test t;
B() : t("T0") {}
B(std::string n) : t(n) {}
};
int fce(B& b)
{
std::cout << "b = B(T2)\n";
b = B("T2");
std::cout << "return 0\n";
return 0;
}
int main() {
B b("T1");
std::cout << "fce call\n";
fce(b);
std::cout << "fce end " << b.t._name << std::endl;
}
移动输出:
fce call
b = B(T2)
Destructor T1
return 0
fce end T2
Destructor T2
不移动输出:
fce call
b = B(T2)
Destructor T2
return 0
fce end T2
Destructor T2
【问题讨论】:
-
使用
move,您的行为未定义,因为您的析构函数访问处于未指定状态的_name。 -
看起来
std::string的实现在移动时进行了交换。因此Destructor T1实际上是B("T2")被移出后的析构函数。我这边的测试产生了Destructor(空字符串)。 -
@Eljay 未指定 但有效 状态,因此它没有达到 UB。
-
不是说它是UB,只是说它是未指定的状态。正如昆汀所澄清的,未指定但有效的状态。在那种状态下,几乎没有人可以使用它。它可以被分配,也可以被破坏。
-
@Eljay "不是说它是 UB,只是说它是未指定的状态。" 但是,你说 "
move,你有未定义的行为" 那是你说的,还是你没说的?