How to fix the leak in this simple Single-Linked List?

Hi everyone,

I have the following simple implementation of a Single-Linked List

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include <iostream>
#include <vector>
#include <cassert>
#include <algorithm>
#include <memory>

template <typename T>
struct Link
{
    T val;
    Link *next;
    Link(const T &vval = T{}, Link *p = nullptr) : val{vval}, next{p} {}
};

template <typename T>
class List
{

private:
    Link<T> *first;

public:
    class Iterator;

    Iterator begin() { return Iterator(first); }
    Iterator end() { return Iterator(nullptr); }

    List() : first{nullptr} {}

    ~List(){ delete first;}
    int size() const;
    void push_front(const T& val);
    void push_back(const T &val);
    Iterator insert(Iterator p, const T &v);

};

template <typename T>
void List<T>::push_front(const T& val){
    first = new Link<T>(val,first);
}

template <typename T>
typename List<T>::Iterator List<T>::insert(Iterator p, const T &v)
{
    Link<T> *tmp = p.current->next;
    Link<T> *new_elem = new Link<T>(v, tmp);
    p.current->next = new_elem;
    return Iterator(new_elem);
}

template <class T>
void List<T>::push_back(const T &v)
{
    Iterator p1 = begin();
    while (true)
    {
        Iterator p2 = p1;
        ++p2;
        if (!p2.current)
        {
            this->insert(p1, v);
            return;
        }
        ++p1;
    }
}

template <typename T>
class List<T>::Iterator
{
    friend class List<T>;
    Link<T> *current;

public:
    Iterator(Link<T> *p) : current{p} {}
    Iterator &operator++()
    {
        current = current->next;
        return *this;
    }
    T &operator*() { return current->val; }

    bool operator==(const Iterator &b) { return current == b.current; }
    bool operator!=(const Iterator &b) { return !(*this == b); }
};

int main()
{

    List<int> l{};
    l.push_front(10);
    l.push_front(20);
    l.push_back(19);
    l.push_back(29);
    for (auto& x:l)
    {
        std::cout << x <<'\n';
    }
    
    return 0;
}


I am already able to do a Linked-List with smart pointers, which means in this particular case that I can use std::unique_ptr. However, I wanted to try with raw ones for learning purposes.

So far, I am having a memory leak, and it's coming from line 47 in the insert function.
How can I fix it? I was thinking to just plug a destructor ~Link(){delete next;} in the Link struct, so when a Link goes out of scope in a function, then its is automatically destroyed, but I don't know if this is the right choice. (Notice that I also deleted first, my head, in ~List )



I'd like to understand how I can fix this. Even other workarounds are much appreciated.

Best
Last edited on
The destructor isn't correct. You need to traverse the list links and delete the allocated memory for each node.
Thanks for your answer @seeplus.

However, if I plug ~Link(){delete next;}, then I see no memory leaks. Why can't this be the correct workaround?

Thanks
Last edited on
I updated my code in this way: notice the destructor of List at line 31: please tell me if I am doing precisely what you meant. I have seen that so far I have no memory leaks.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#include <iostream>
#include <vector>
#include <cassert>
#include <algorithm>
#include <memory>

template <typename T>
struct Link
{
    T val;
    Link *next;
    Link(const T &vval = T{}, Link *p = nullptr) : val{vval}, next{p} {}
};

template <typename T>
class List
{

private:
    Link<T> *first;

public:
    class Iterator;

    Iterator begin() { return Iterator(first); }
    Iterator end() { return Iterator(nullptr); }

    List() : first{nullptr} {}

    ~List()
    {
        auto it = begin();

        while (it != this->end())
        {
            delete it.current;
            ++it;
        }
    }
    int size() const;
    void push_front(const T &val);
    void push_back(const T &val);
    Iterator insert(Iterator p, const T &v);

};

template <typename T>
void List<T>::push_front(const T &val)
{
    first = new Link<T>(val, first);
}

template <typename T>
typename List<T>::Iterator List<T>::insert(Iterator p, const T &v)
{
    Link<T> *tmp = p.current->next;
    Link<T> *new_elem = new Link<T>(v, tmp);
    p.current->next = new_elem;
    return Iterator(new_elem);
}

template <class T>
void List<T>::push_back(const T &v)
{
    Iterator p1 = begin();
    while (true)
    {
        Iterator p2 = p1;
        ++p2;
        if (!p2.current)
        {
            this->insert(p1, v);
            return;
        }
        ++p1;
    }
}

template <typename T>
class List<T>::Iterator
{
    friend class List<T>;
    Link<T> *current;

public:
    Iterator(Link<T> *p) : current{p} {}
    Iterator &operator++()
    {
        current = current->next;
        return *this;
    }
    T &operator*() { return current->val; }

    bool operator==(const Iterator &b) { return current == b.current; }
    bool operator!=(const Iterator &b) { return !(*this == b); }
};

int main()
{

    List<int> l{};
    l.push_front(10);
    l.push_front(20);
    l.push_back(19);
    l.push_back(29);
    for (auto &x : l)
    {
        std::cout << x << '\n';
    }
    l.push_back(29);
    l.push_front(2);
    l.push_back(29);
    l.push_front(2);
    for (auto &x : l)
    {
        std::cout << x << '\n';
    }


    return 0;
}
Last edited on
1
2
3
4
5
6
7
8
9
10
 ~List()
    {
        auto it = begin();

        while (it != this->end())
        {
            delete it.current;
            ++it;
        }
    }


I don't think so. ++it does current = current->next - but you're just deleted current!
You need to get the address of the next node before you delete the memory of the current one. Without using iterators, the usual code would be something like:

1
2
3
4
5
6
while (head != nullptr) {
    const auto cur {head};

    head = head->next;
    delete cur;
}

Last edited on
Thanks @seeplus, I've understand the issue now. I have just written the following destructor, but using iterators now. I think it's doing the same thing you wrote in your last snippet. Do you agree? After this check, I can consider the topic closed :-)

1
2
3
4
5
6
7
8
9
~List(){
auto it = begin();
while (it!=nullptr)
{
    const auto current_node = it.current;
    ++it;
    delete current_node;
}
}
Last edited on
1
2
3
4
5
6
for (auto it {begin(); it != end(); ) {
    const auto current_node {it.current};

    ++it;
    delete current_node;
}


assuming that end() returns nullptr which I think it does from your code above.
Ah yes, that for loop is really the classical way to traverse a container... don't know why I wrote that while. Btw, thanks a lot for your help @seeplus

Now things are much more clear. :-)
Topic archived. No new replies allowed.