首页 > 解决方案 > 默认移动赋值调用析构函数,复制赋值不调用

问题描述

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

假设我有一个B具有默认所有内容的类和一个Test具有自定义析构函数、默认复制分配和(可能)默认移动分配的类。

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

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

  1. Test没有move assignment(因此调用它的复制赋值)时,T1对象的析构函数没有被显式调用。我认为在这种情况下,惯用的做法是将资源作为copy assignment. 但是,为什么move assignment存在(并被调用)时会有所不同?如果它在那里,Test则显式调用 ' 析构函数(?由操作员)。
  2. 该文档指定other后移动分配可以保留在任何状态。=B("T2")如果 B 的成员没有,为什么不调用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

标签: c++move

解决方案


默认移动赋值调用析构函数,复制赋值不调用

这两个赋值都会导致临时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"临时对象在被移出后不再处于“包含”所描述的状态。


推荐阅读