移动重载和析构函数理论。如果我是对的,有人可以回答吗?

Move overload and destructor theory. Can someone please answer if I'm right?

我正在尝试了解 C++ 移动重载和析构函数调用的权利。 所以我做测试。我的理论: returns foo_test() 的“foo”由移动构造函数创建,返回(作为副本??),然后“foo in”被销毁。从 foo_test() 返回的“foo”带有分配给“foo my”的 move=overload,然后被销毁。之后,“my”被打印并销毁,因为它超出了范围。 我接近正确答案了吗?

Main.cpp

#include <iostream>

class foo {
    int* p;
    int s;
public:
    foo(int in)
        :s{ in }, p{ new int[in] }{
        for (int i = 0; i < s; ++i)
            p[i] = i;
    }
    foo(foo&& a)
        :s{ a.s }, p{ a.p } {a.s = 0; a.p = nullptr; std::cout << "move cons was called" << std::endl; }
    foo& operator=(foo&& a) {
        delete[] p;
        p = a.p;
        s = a.s;
        a.s = 0;
        a.p = nullptr;
        std::cout << "move = overload was called";
        return *this;
    }

    void print() {
        for (int i = 0; i < s; ++i)
            std::cout << p[i];
    }
    
    ~foo() {
        std::cout << std::endl << "foo destructor" << s << std::endl;
    }
};

foo foo_test() {
    foo in{10};
    return in;
}

int main()
{
    foo my{ 5 };
    my.print();
    std::cout << std::endl;
    my = foo_test();
    my.print();
}

输出

01234
move cons was called

foo destructor0
move = overload was called
foo destructor0
0123456789
foo destructor10

移动运算符的存在与否对构造函数和析构函数的工作方式没有任何影响。每个对象都是通过其析构函数构造的,并在某个时候被销毁。这是 C++ 的基础。移动语义不会改变这一点。这是首先要了解的重要事情。

移动构造函数只是另一个构造函数,算作一个构造函数。移动赋值运算符完全无关紧要。移出的对象仍然必须在某个时候被销毁。移动到的对象是先前构造的。这两个对象在某个时候都会被构造和销毁。

foo foo_test() {
    foo in{10};
    return in;
}

但是这里你引入了另一个因素:复制省略。在这里,从 C++11 开始,允许(但不是必需)编译器从 foo_test().

完全 elide the copy when returning in

通常 return 从函数中调用任何对象都会执行隐式复制(或移动)。但是在这里,根据编译器的突发奇想,不会发生复制或移动。 in 对象实际上是在调用者的上下文中构建的,而 return 是一个无足轻重的东西。它并没有真正的“return”任何东西。在调用者的上下文中,一旦 returned 它已经有一个完全烘焙的对象,在它的位置。然而,这里:

my = foo_test();

因为 returned 值被分配给现有对象,所以在某些时候必须调用其赋值或移动运算符。然后,在 foo_test 中构造的对象被销毁(无论是否由于 return 而删除其隐式副本)。

因此,您的问题的答案是:此处发生的情况是……视情况而定。这取决于您的编译器决定做什么。

了解这里到底发生了什么并不重要。重要的是要了解:

  1. 在 C++ 中,每个对象都会被构造和销毁。
  2. 移动运算符的存在与否不会改变这一点。在对象被构造或销毁后,它可能会被移至或移出,但不会影响其构造或销毁。
  3. 对于前两个规则,对象移动构造函数算作构造函数。
  4. C++ 具有可选或强制的复制省略,具体取决于 C++ 版本。复制省略所做的只是有效地使复制消失。它仍然没有改变所有对象被构造和销毁的规则。
  5. 复制省略可能做的就是将两个对象“折叠”为一个对象,当隐式复制作为从一个函数的 returning 的结果发生时。这是前两条规则可以弯曲的最远距离。实际上,在被调用者和调用者的上下文中,这两个对象成为一个对象,遵循相同的规则,否则。