【问题标题】:C++11 Move Assignment and Copy Assignment (operators) - Use of deleteC++11 移动赋值和复制赋值(运算符)——删除的使用
【发布时间】:2015-08-27 00:40:59
【问题描述】:

我目前的理解是 两者 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

查看这篇wikipedia 文章中的示例代码,我很清楚:

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

  • 但是,我对 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(无操作)。

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

【问题讨论】:

    标签: c++11 memory-leaks move-semantics raw-pointer


    【解决方案1】:

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

    nullptr 上的delete 是安全的,并且不会像您指出的那样执行任何操作。

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

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

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

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

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

    正确。

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

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

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

    正确。

    【讨论】:

      猜你喜欢
      • 2017-11-21
      • 1970-01-01
      • 2020-05-16
      • 1970-01-01
      • 1970-01-01
      • 2018-09-21
      • 1970-01-01
      • 2017-04-19
      • 2015-06-23
      相关资源
      最近更新 更多