0

我目前的理解是C++11 移动和复制赋值运算符应该调用 delete 以防止内存泄漏,但 C++11 移动和复制构造函数不应该

如果我的理解是正确的,构造函数不需要调用delete,但是我不确定为什么。考虑以下示例:

class my_class
{
    my_class(int num_data) : my_data{new double[num_data]}, my_data_length{num_data}
    {
    }

    // This class manages a resource
    double *my_data;
    int my_data_length;
}

// Big 4 go here - see wikipedia example below.

my_class a(10);
my_class b(10);
my_class c(10);

a = b; // Need to delete old storage contained in a before reallocating
a(c); // For some reason we don't need to delete the old storage here? I find this puzzling

查看此维基百科文章中的示例代码,我很清楚:

  • Move Constructor 不会因为资源被转移而泄漏。任何由到期的类指向的已分配数据都将转移到即将到期的类,并由即将到期的类的析构函数删除。

  • 但是,我对 Copy Constructor 是否泄漏感到困惑。

  • 移动赋值运算符可能不会泄漏,因为它只是交换指针。

  • 我再次对复制分配运算符感到困惑。我不确定为什么需要使用临时对象?我的猜测是在此函数结束时超出范围时所拥有的资源并被销毁tmpother(除了tmp它的资源与类中的指针交换了this吗?)

为方便起见,下面提供了代码:

#include <cstring>
#include <iostream>

class Foo
{
public:
    /** Default constructor */
    Foo() :
        data (new char[14])
    {
        std::strcpy(data, "Hello, World!");
    }

    /** Copy constructor */
    Foo (const Foo& other) :
        data (new char[std::strlen (other.data) + 1])
    {
        std::strcpy(data, other.data);
    }

    /** Move constructor */
    Foo (Foo&& other) noexcept : /* noexcept needed to enable optimizations in containers */
        data(other.data)
    {
        other.data = nullptr;
    }

    /** Destructor */
    ~Foo() noexcept /* explicitly specified destructors should be annotated noexcept as best-practice */
    {
        delete[] data;
    }

    /** Copy assignment operator */
    Foo& operator= (const Foo& other)
    {
        Foo tmp(other); // re-use copy-constructor
        *this = std::move(tmp); // re-use move-assignment
        return *this;
    }

    /** Move assignment operator */
    Foo& operator= (Foo&& other) noexcept
    {
        // simplified move-constructor that also protects against move-to-self.
        std::swap(data, other.data); // repeat for all elements
        return *this;
    }

private:
    friend std::ostream& operator<< (std::ostream& os, const Foo& foo)
    {
        os << foo.data;
        return os;
    }

    char* data;
};

int main()
{
    const Foo foo;
    std::cout << foo << std::endl;

    return 0;
}

我想这暗示了为什么设置(未初始化/未分配)悬空指针很重要nullptr,因为这将防止在删除析构函数时出现内存错误?

我认为这是因为资源通过移动构造函数传输的情况,但是过期对象接收到一个从未分配过的悬空指针——我们不希望随后调用析构函数和delete指针——除非我们确保它指向nullptr(无操作)。

谁能澄清我提出的一些观点?

4

1 回答 1

0

移动构造函数和赋值运算符将数据指针设置为,nullptr因为指针的所有权正在被移动。如果他们没有,并且在原始和新的上调用了 delete,那么您将进行双重删除。或者如果旧的被删除,新的指针就会无效。同时,复制构造函数和赋值运算符正在创建一个完全独立的数据副本。

deleteonnullptr是安全的,并且正如您所指出的那样不执行任何操作。

Move Constructor 不会因为资源被转移而泄漏。任何由未到期的类指向的已分配数据都将转移到即将到期的类,并由即将到期的类的析构函数删除。

是的,移动构造函数不会因为资源被转移而泄漏。更重要的是,它从不分配自己的资源。

但是,我对 Copy Constructor 是否泄漏感到困惑。

复制构造函数使原始数据保持原样,并对数据进行深层复制。所以这两个对象都会清理自己的数据。

移动赋值运算符可能不会泄漏,因为它只是交换指针。

正确的。

我再次对复制分配运算符感到困惑。我不确定为什么需要使用临时对象?我的猜测是 tmp 和其他拥有的资源在此函数结束时超出范围时会被销毁?(除了 tmp 将其资源与此类中的指针交换?)

复制赋值首先使用复制构造函数创建一个新副本,然后使用移动赋值运算符将新副本移动到对象中,并将旧数据移动到临时对象中。然后临时超出范围,调用析构函数并删除旧数据。

我想这暗示了为什么将(未初始化/未分配的)悬空指针设置为 nullptr 很重要,因为这将防止在删除析构函数时出现内存错误?

正确的。

于 2015-08-27T01:21:14.670 回答