【问题标题】:How can I delete a linked list without memory leak?如何删除没有内存泄漏的链表?
【发布时间】:2019-10-06 07:25:04
【问题描述】:

我正在尝试制作电话簿,并将我的数据存储在链接列表中。但是,当我尝试删除它时,我要么得到内存泄漏,要么得到一些丑陋的退出状态,并从内存中获取一些值。我猜这个退出状态表明在我的列表删除过程中出现了一些问题,因为程序不会进入下一行。

我试过来回弄乱析构函数,但似乎我无法自己解决这个问题。

我有三个重要的课程。一个用于电话簿,一个用于联系人,一个用于联系人的数据成员。 Data 类是抽象的,并且有一些派生类。我还有一个链表结构,用于将联系人存储在电话簿中,并将数据存储在联系人中。

链表结构:

#include "Data.h"
class Contact; //to avoid an "undefined reference" error

struct ListMem{
    ListMem* next=NULL;
    Contact* cont=NULL;
    Data* dat=NULL;
}; //this class can't have a destructor that calls "delete cont",
//because of the order I declared my classes in

电话簿类:

#include "Contact.h"
#include "ListMem.h"

class Phonebook{
    size_t cont_num; //how many contacts are already stored
    ListMem* cont; //pointer to the first member of the linked list
public:
    Phonebook(const char* filename){ //reads the Phonebook from file
        std::ifstream is;
        is.open(filename);
        size_t pb_size;
        string line;
        ListMem* tmp;
        is>>pb_size; getline(is, line);
        cont_num=0;

        for(size_t i=0; i<pb_size; i++){
            getline(is, line);
            if(i==0){
                Contact* tmp_ct=new Contact(line);
                cont=add_cont(tmp_ct);
                tmp=cont;
            }
            else {
                Contact* tmp_ct=new Contact(line);
                tmp->next=add_cont(tmp_ct);
                tmp=tmp->next;
            }
        }
    }

    ListMem* add_cont(Contact* new_ct){ //adds a new contact
        ListMem* tmp=new ListMem;
        tmp->cont=new Contact(new_ct);
        cont_num++;
        return tmp;
    }

    ~Phonebook(){
        ListMem* tmp=cont;
        while(tmp!=NULL){
            ListMem* del=tmp;
            tmp=tmp->next;
            delete del->dat;
            delete del->cont;
            delete del;
        }
    }
};

联系人类:

#include "Name.h"
#include "ListMem.h"

class Contact{
    size_t data_num; //number of data stored
    ListMem* data; //pointer to the first data member
public:
    Contact(string line){ //converts a "line" into a Contact
        ListMem* tmp;
        int dat_count=std::count(line.begin(), line.end(), ':');
        data_num=dat_count;
        string dat, tmp_dat, tmp_type;
        int pos_1=0, pos_2=line.find(';');

        for(int i=0;i<dat_count;i++){
            dat=line.substr(pos_1, pos_2-pos_1);
            tmp_type=dat.at(0);
            tmp_dat=dat.substr(2, dat.size()-2);
            pos_1+=dat.size()+1;
            pos_2=line.find(';', pos_1);
            if(i==0){
                data=add_data(tmp_type, tmp_dat);
                tmp=data;
            }
            else {
                tmp->next=add_data(tmp_type, tmp_dat);
                tmp=tmp->next;
            }
        }
    }

    ListMem* add_data(string type, string data){ //adds a new data member
            ListMem* tmp=new ListMem;
            if(type=="1") tmp->dat=new Name(data);
            //I have more, but it's irrelevant.
            data_num++;
            return tmp;
    }

    ~Contact(){
        ListMem* tmp=data;
        while(tmp!=NULL){
            ListMem* del=tmp;
            tmp=tmp->next;
            delete del->dat;
            delete del->cont;
            delete del;
        }
    }
};

数据类:

class Data{
    string type;
public:
    Data(){}
    void set_type(string t) {type=t;}
    virtual ~Data(){}
};

其中一个派生类,其他看起来基本一样:

#include "Data.h"

class Name: public Data{
    string name;
public:
    Name(string n): name(n){ set_type("1");}
    ~Name(){}
};

当然还有主要的:

#include "Phonebook.h"

int main(){
    Phonebook* Test=new Phonebook("test.txt");
    delete Test;

    return 0;
}

在“test.txt”文件中,我有我的测试电话簿:

3
1:test_name_1;
1:test_name_2;
1:test_name_3;

所以当我运行这段代码时,我有内存泄漏,我不知道为什么。我让析构函数沿着链表运行并释放动态内存,或者至少我是这么想的。

我知道我没有按我应该的方式释放联系人,因为我使用了内存泄漏检查器工具,并且模式重复我拥有的联系人的相同时间。

我使用了 .cpp 文件,但我在此处集成了相关部分以节省一些空间。另外,我没有把我的项目系统以外的包含放在这里。

我强烈怀疑问题出在析构函数上,~Phonebook()、~Contact() 或两者兼而有之。我把其他部分放在这里是因为有人告诉我要发布一个任何人都可以轻松复制的版本。我觉得肯定有一个愚蠢的错误和一个简单的解决方案,但我不知道是什么。

你们能帮帮我吗?

【问题讨论】:

  • "当我尝试删除它时,我要么得到内存泄漏,要么得到一些丑陋的退出状态,并从内存中获取一些值。"你为什么不发布这些?无论如何,Phonebook:add_cont 中的行 ListMem* tmp=new ListMem; tmp-&gt;cont=new Contact(new_ct); 看起来很可疑。这甚至是如何编译的?
  • A minimal reproducible example 会更容易诊断。作为奖励,创建一个最小但完整的示例的过程可能会告诉您错误在哪里。 (为了完整起见,示例不应依赖于数据文件。尝试使用硬编码的虚拟数据创建示例,而不是从文件中读取。)
  • @Quimby 抱歉,我认为它们不会有任何用处。我使用了学校提供的内存跟踪程序,所以我不确定输出是否标准。下次我一定会把它们包括在内。是的,这就是问题所在,谢谢。但是你为什么要问这甚至是如何遵守的?编译器应该注意到这样的事情吗?
  • @Heves 遗憾的是,它通常不会/不能,但我的意思是 new Contact(new_ct) 调用构造函数 Contact::Contact(Contact* ptr) 并且没有。如果它是Contact(*new_ct),那么将调用一个存在的复制ctor。这只是一个错字吗?如果不是,那么编译器应该抱怨。除此之外,发布错误消息不会造成任何伤害,而且通常很有帮助。例如。 valgrind 通常至少可以指向创建泄漏对象的分配代码。但很高兴看到问题得到解决:)。
  • 还有new ListMem;new ListMem()之间的区别;见stackoverflow.com/questions/620137/…

标签: c++ pointers linked-list destructor


【解决方案1】:

电话簿构造函数解析文件,使用 new 运算符创建联系人对象并将其传递给 add_cont()。成员函数 add_cont() 使用 new 运算符再次创建作为参数传递的 Contact 对象的副本,但从不删除原点。 从我的角度来看, add_cont() 'new_ct' 可以分配给 'tmp->cont'。

顺便说一句,更好的方法是使用可用的容器类,如 std::list、std::vector 等,并避免使用原始指针。

【讨论】:

  • 感谢您的回答,您是对的。我为学校做这个项目,他们告诉我们不要使用 STL 容器,这就是我玩链表的原因。我们只学习基本的 C++,它们并没有真正教给我们最新的特性,或者 C++11 或任何关于“非原始”指针的东西。这可以说在现实生活中很有用,但学校就是这样。再次感谢您。
猜你喜欢
  • 2014-09-21
  • 2017-09-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多