默认移动分配调用析构函数,复制分配不

时间:2019-03-27 11:50:15

标签: c++ move

我观察到关于析构函数以及(默认)复制和移动分配我不太了解的奇怪行为。

比方说,我有一个B类,该类具有默认的所有内容,而一个类Test具有自定义析构函数,默认副本分配和(可能)默认移动分配。

然后,我们创建B的实例,将其分配给变量,然后使用赋值(其中右侧为rvalue)替换为新实例。

两件事对我来说似乎很奇怪,我在文档中看不到它们的原因。

  1. Test没有move assignment时(因此将其副本分配调用),未明确调用T1对象的析构函数。我假设在这种情况下,惯常做法是将资源清理为copy assignment的一部分。但是,move assignment在那里(并被调用)为什么又不同呢?如果Test的析构函数在那里,则被显式调用(由运算符调用?)。
  2. 文档指定移动分配后的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

1 个答案:

答案 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"”所描述的状态。