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

Default move assignment calls destructor, copy assignment doesn't

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

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

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

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

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

Default move assignment calls destructor, copy assignment doesn't

两次赋值都会导致临时 B 对象的销毁,因此会调用析构函数。

replace with a new instance using assignment

迂腐的注解:赋值不会替换实例。实例保持不变;实例的值被修改。这种区别可能很微妙,但也可能与您的困惑有关。

When Test doesn't have move assignment (thus its copy assignment is called) the destructor of T1 object isn't explicitely called.

您所说的 "T1 object" 有点不清楚。您使用 "T1" 初始化的变量 b 被销毁。但是当它被销毁时,它的值之前已经分配给 "T2",所以这就是析构函数插入 cout 的内容。这在移动和复制情况下都会发生,这是输出中的第二行 Destructor TX

Why is it different when move assignment is there (and called), however?

不同之处在于 b = B("T2") 行中的临时对象何时被销毁。这是输出中的第一行 Destructor TX

复制赋值后,这个临时变量仍将保留 "T2" 值,这就是您在析构函数中看到的。

移动分配后,不再保证临时文件包含 "T2",而是保留在有效但未指定的状态(如 std::string 规范中所述),因此输出可以是任何东西。在这种情况下,它恰好是 "T1"。 (根据这个结果,我们可能会猜测字符串的移动赋值运算符可能是通过交换内部缓冲区来实现的。这种观察结果不是保证的行为)。

The documentation specifies that the other after move assignment can be left in whatever state. How come the destructor for T2's temporal rvalue (i.e. the right side of =B("T2")) isn't called in case B's member doesn't have move assignment?

临时的析构函数被调用。临时文件在被移动后不再处于 "contains "T2"" 所描述的状态。