我观察到关于析构函数以及(默认)复制和移动分配我不太了解的奇怪行为。
比方说,我有一个B
类,该类具有默认的所有内容,而一个类Test
具有自定义析构函数,默认副本分配和(可能)默认移动分配。
然后,我们创建B
的实例,将其分配给变量,然后使用赋值(其中右侧为rvalue)替换为新实例。
两件事对我来说似乎很奇怪,我在文档中看不到它们的原因。
Test
没有move assignment
时(因此将其副本分配调用),未明确调用T1
对象的析构函数。我假设在这种情况下,惯常做法是将资源清理为copy assignment
的一部分。但是,move assignment
在那里(并被调用)为什么又不同呢?如果Test
的析构函数在那里,则被显式调用(由运算符调用?)。other
可以保持任何状态。如果B的成员没有=B("T2")
的话,如何不调用T2的时间右值的析构函数(即move assignment
的右侧)? 游乐场代码: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
答案 0 :(得分:5)
默认移动分配调用析构函数,复制分配没有
这两种分配都会导致临时B
对象的破坏,因此将调用析构函数。
使用分配替换新实例
请注意:分配不会替换实例。实例保持不变;实例的值被修改。这种区别可能微妙,但也可能与您的困惑有关。
当Test没有移动分配(因此称为复制分配)时,不会明确调用T1对象的析构函数。
“ T1对象”的含义尚不清楚。您用b
初始化的变量"T1"
被销毁。但是,销毁它时,它的值先前已分配给"T2"
,因此析构函数将其插入cout
中。在移动和复制情况下都会发生这种情况,这是输出中的第二行Destructor TX
。
但是,当有(并称为)移动分配时,为什么有什么不同?
区别在于b = B("T2")
行中的临时对象被销毁时。这是输出中的第一行Destructor TX
。
分配副本后,该临时文件仍将保留"T2"
值,这就是您在析构函数中看到的内容。
移动分配后,不再保证临时文件包含"T2"
,而是将其保留为有效但未指定的状态(如std::string
的说明中所述),因此输出可以是任何内容。在这种情况下,它恰好是"T1"
。 (基于此结果,我们可能会猜测可能是通过交换内部缓冲区实现了字符串的移动赋值运算符。这种观察不能保证得到保证。)
文档指定移动后分配中的另一个可以保留在任何状态。如果B的成员没有移动分配,怎么不调用T2的时间右值的析构函数(即= B(“ T2”)的右侧)?
调用临时的析构函数。临时文件从其移出后,不再只是处于“包含"T2"
”所描述的状态。