【问题标题】:Destructor deletes copy in function returning dynamic structure析构函数删除返回动态结构的函数中的副本
【发布时间】:2010-11-21 14:12:52
【问题描述】:

大家好,菜鸟问题。

所以我有一个实现单链表的模板类。我的程序中的类中的函数返回这些列表之一。

psList<int> psObj::getList() const {
 return List;
}

所以发生的事情是在调用返回 List 时复制构造函数启动,它很好地完成了它的工作并创建了列表的副本。然而,函数完成并超出范围并调用析构函数!突然返回的链表被删除,因为这是我的析构函数所做的,删除一个列表并很好地删除它。

我知道我可以将返回类型设置为指向复制列表头部的指针,一切都会很好,但问题是我仍然无法创建返回动态结构副本的函数,即使我想这样做,我也确实想这样做。

有人要求我提供更多代码。

这里是拷贝构造函数,显然是做深拷贝

template<class psClass>
psList<psClass>::psList(const psList &original) {
    head = NULL;

    if(original.head != NULL) {
        psNode<psClass>* iterator = original.head;
        while(iterator != NULL) {
            pushback(iterator->data);
            iterator = iterator->next;
        }
    }
}

这是析构函数

template<class psClass>
psList<psClass>::~psList() {
    erase();
}

这是析构函数调用的擦除函数。

template<class psClass>
void psList<psClass>::erase() {
    psNode<psClass>* iterator = head;
    psNode<psClass>* buff;

    while(iterator != NULL) {
        buff = iterator->next;
        delete iterator;
        iterator = buff;
    }
}

所以是的,我正在做深度复制和深度破坏。问题不在于深度。问题是这样的。在原始函数中,制作并返回深层副本。该函数超出范围,并在副本上调用深度析构函数。不再复制。

为了更好地解释这里是调试器中的样子

getlist 函数调用之前的原始列表。

head 0x616080
data 2
next 0x616060
data 12
next 0x0

这是 getList 函数中“返回列表”的列表

head 0x616080
data 2
next 0x616060
data 12
next 0x0

同样的事情。

这里是复制构造函数末尾的“original”和“this”列表。

“这个”

head 0x63c900
data 2
next 0x63a940
data 12
next 0x0

“原创”

head 0x616080
data 2
next 0x616060
data 12
next 0x0

一切看起来都很棒不是吗。

现在我们回到了 getList 函数,即将进入最后一个括号。

psList<int> psObj::getList() const {
 return List;
} // This bracket

这个函数后面的列表List就是你所期望的

head 0x616080
data 2
next 0x616060
data 12
next 0x0

现在我们进入最后一个括号,析构函数在哪里被调用

/* 
 * No idea what the in chrg thing is or why the debugger is telling me it was
 * optimized out but I mentioned it here cause maybe it has something to do with my
 * problem
 */
this 0x7ffffffe650
__in_chrg value optimized out

// Look familiar? well it should cause it is the head of the list I returned.
head 0x63c900 
data 2
next 0x63a940
data 12
next 0x0

然后砰!我刚刚复制并返回的列表被析构函数删除,因为它超出了范围。

在那次绕道之后重申我最初的问题。如何让函数使用深层副本返回动态结构,而不让析构函数破坏所述副本。

应要求提供更多代码

// Simple single link node with default constructor initializing the link to NULL.
template <class psClass>
struct psNode {
    psClass data;
    psNode<psClass>* next;

    psNode() {
        next = NULL;
    }
};

还有推回功能

template<class psClass>
void psList<psClass>::pushback(psClass object) {
    psNode<psClass>* ptr = new psNode<psClass>;
    ptr->data = object;

    if(head == NULL)
        head = ptr;
    else {
            //Have to find the tail now
        psNode<psClass>* tail;
        psNode<psClass>* iterator = head;
        while(iterator != NULL) {
            tail = iterator;
            iterator = iterator->next;
        }
        tail->next = ptr;
    }
}

是的,我知道跟踪 tail 会更容易。

这是 psList 类定义:

template <class psClass>
class psList {
public:
    psList();
    ~psList();
    psList(const psList &original);
    psList(psNode<psClass>* _head);

    void erase();
    void pushfront(psClass object);
    void pushback(psClass object);
    bool isEmpty() const;
    psNode<psClass>* front() const;

private:
    psNode<psClass>* head;
};

还没有重载的赋值运算符。我打算在我跳过这个障碍后添加它。

【问题讨论】:

  • 您应该熟悉The Rule of Three。此外,除非这是家庭作业或学习练习,否则绝对建议实施您自己的链表。
  • 你返回的链表应该被销毁,当然,因为它已经在return语句中复制了,而原始的对于你返回的函数来说是本地的?
  • 您可能应该展示如何定义psListpsNodepsList::pushback
  • 这是有道理的,但函数本地的一个和我返回的一个是内存中的 same list。如果它不是一个动态结构,那么我会声明 someVar,返回 someVar,它会创建一个自身的副本,然后函数中的 someVar 将被删除。但是现在发生的事情是我声明了一些列表,我返回了一些列表,并且由于列表是内存中的地址,我也删除了该列表。也许我正在尝试做一些不可能的事情,但你肯定可以返回一个动态结构而不会以某种方式破坏它。我将提供一些调试器的输出以便更好地解释。
  • 我以为您在复制构造函数中进行“深度”复制?如果是这样,它们在内存中不是同一个列表,返回的值是函数本地的一个副本。

标签: c++ constructor destructor


【解决方案1】:

看起来psList 的复制构造函数会生成浅拷贝而不是深拷贝。一般来说,如果您在一个类中管理资源,那么您需要非平凡的复制构造函数、赋值运算符和析构函数(“三巨头”)。请给我们psList的代码。

【讨论】:

  • 不,深度复制和深度破坏两者
【解决方案2】:

你目前正在做的实际上如下:

psList<int> psObj::getList() const { return psList<int>(List); }

这会创建成员List 的副本并将其复制到调用getList 的框架中。它被复制的方式取决于你如何称呼它。如果你从这个数据中构造一个新的psList 对象,如

psList<int> newList = obj.getList(); // same as psList<int> newList(obj.getList());

使用了复制构造函数。通常,RVO 会将两个副本缩减为一个副本。

或者,如果您要复制到现有对象,如

psList<int> newList;
newList = obj.getList();

原始对象的状态通过赋值运算符替换为返回结果中的数据。如果您不声明自己的一个,编译器将为您定义一个公共赋值运算符。但这只是对象每个成员的副本。那就是:

psList & psList::operator=(const psList& src) {
  head = src.head;
}

所以在调用代码中发生的情况如下:

psList<int> newList; // psList default constructor called
newList = obj.getList(); // 1) obj.List copied via copy constructor within getList
                         // 2) copy of obj.List copy-assigned to newList (simple copy of head pointer)
                         // 3) copy of obj.List destructed
// newList now has head pointing to destroyed data

在您的情况下,这不是您想要的,您应该做的是确保复制分配真正执行预期的深层复制(请参阅copy-and-swap,了解通过您已经拥有的复制构造函数执行此操作的方法实施)。

因此rule of three:如果您需要定义自己的析构函数、复制构造函数和复制赋值中的任何一个的实现,那么您需要将它们全部定义(或者至少声明复制赋值和复制ctor私有到使您的课程不可复制)。

顺便说一句,为什么不返回参考:

const psList<int> & psObj::getList() const { return List; }

并让调用函数决定是否复制?

psList<int> localList(localPsObj.getList());

【讨论】:

  • 你是怎么得出这个结论的?构造函数的未知部分(pushback)只接收psNodedata 部分,因此它很难重建指向原点psNode 的指针。
  • 不,复制构造函数绝对会进行深层复制。 pushback 创建一个指向空白新节点的指针,将数据复制到该节点,然后将已创建列表中的最后一个节点链接到它(在它链接到 null 之前)。新节点中的链接已指向 null,因此无需在此处进行任何操作。
  • @Charles:好点,错过了只有数据传递给pushback。用希望更有用的东西替换了我的垃圾答案。
  • 原谅我的无知,但为什么我需要做类似 psList psObj::getList() const { return psList(List);据我了解,当一个函数需要返回一个值时,它会调用复制构造函数。这不是不必要地调用复制构造函数两次吗?
  • @Avram:检查 Strousrup TCPP 我认为没有区别 - 值返回行中有一个隐式副本,我只是明确表示。
猜你喜欢
  • 2012-08-11
  • 1970-01-01
  • 2015-12-31
  • 2019-10-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-21
  • 1970-01-01
相关资源
最近更新 更多