0

Hello I have a bit ambiguity about writing a proper destructor:

class SLLst 
{
public:
    SLLst() = default;
    SLLst(const SLLst&);
    SLLst& operator=(SLLst);
    ~SLLst();

    void insert(int);
    void remove(int);

private:
    SLLst* next = nullptr;
    int data = 0;
    friend void swap(SLLst&, SLLst&);
    friend std::ostream& print(std::ostream&, const SLLst&);
};

SLLst::SLLst(const SLLst& rhs) : 
    next(rhs.next ? new SLLst() : nullptr),
    data(rhs.data)
{
    cout << "cpy-ctor" << endl;
}

SLLst& SLLst::operator=(SLLst rhs)
{
    cout << "operator=(SLLst)" << endl;
    using std::swap;
    swap(*this, rhs);
    return *this;
}

void swap(SLLst& lhs, SLLst& rhs)
{
    cout << "operator=(SLLst)" << endl;
    using std::swap;
    swap(lhs.next, rhs.next);
    swap(lhs.data, rhs.data);
}

SLLst::~SLLst()
{
    cout << "dtor" << endl;
    delete next;// is this enough?

    // or should I use this code?
    //SLLst* cur = next;
    //SLLst* n = nullptr;

    //while (cur != NULL) {
    //  n = cur->next;
    //  cur->next = nullptr;
    //  delete cur;
    //  cur = n;
    //}

}


void SLLst::insert(int x)
{
    SLLst* tmp = new SLLst();
    tmp->data = x;
    if (!next)
    {
        next = tmp;
        return;
    }

    tmp->next = next;
    next = tmp;
}

std::ostream& print(std::ostream& out, const SLLst& lst)
{
    auto tmp = lst.next;
    while (tmp)
    {
        out << tmp->data << ", ";
        tmp = tmp->next;
    }
    return out;
}

As you can see if I just use delete next; in destructor then I get it called as many as nodes in the list but why many implementations use a loop to free the nodes like the commented code in destructor?

  • Because if I only call delete on next then the destructor will be called recursively thus I think I don't need a loop to free the nodes in destructor? is it correct?

  • When should I use a loop to free nodes in destructor? Thank you!

*If I run my code I'll get:

81, 77, 57, 23, 16, 7, 5,

done
dtor
dtor
dtor
dtor
dtor
dtor
dtor
dtor
  • As you can see dtor is called 8 times; does this mean it has properly freed all the nodes?
Maestro
  • 2,512
  • 9
  • 24

2 Answers2

4

As you can see if I just use delete next; in destructor then I get it called as many as nodes in the list

Yep.

Because if I only call delete on next then the destructor will be called recursively thus I think I don't need a loop to free the nodes in destructor? is it correct?

Yep.

When should I use a loop to free nodes in destructor?

When you need to.

but why many implementations use a loop to free the nodes like the commented code in destructor?

Because many implementations are "C-like", and do not use destructors. So they need to.

You are making the most of C++'s object management features to "do the loop for you". Yay!

(Although, to be honest, I would still do it in a loop, because your way is potentially quite stack-heavy.)

Now go one step further and switch to std::list (or std::forward_list).

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • std::forward_list, std::list is doubly linked – JohnkaS Dec 27 '19 at 20:53
  • @JohnkaS Didn't mean to suggest it were functionally equivalent, but yeah ok – Lightness Races in Orbit Dec 27 '19 at 20:54
  • Thank you so much! really amazing and thorough explanation! – Maestro Dec 27 '19 at 21:00
  • 2
    Yeah my favourite bit is "yep" :P – Lightness Races in Orbit Dec 27 '19 at 21:00
  • One last thing: Is loop more effective than recursive in this case? I mean as a matter of performance. (recursion overhead). – Maestro Dec 27 '19 at 21:01
  • @JohnkaS: In a real example I should use STL Containers like `forward_list` and `list`... but sometimes we should implement our own versions. – Maestro Dec 27 '19 at 21:05
  • I can't think of any reason to implement your own linked list in C++. Regardless, you can measure the performance characteristics of these containers in your situations during different operations on your computer and decide for yourself what is best for you. – Lightness Races in Orbit Dec 27 '19 at 21:07
  • 2
    @Maestro done right there will be little to no performance difference between recursion and iteration. The recursion overhead is typically in the amount Automatic storage consumed. A long enough list will exhaust the stack (or whatever's backing Automatic storage) by storing `this` and the book-keeping information needed for finding the way back. Or maybe the compiler will see what you're doing and loop-ify the job for you. Compilers can be really smart if you let them. [See the Asif Rule](https://stackoverflow.com/questions/15718262/what-exactly-is-the-as-if-rule) – user4581301 Dec 27 '19 at 21:21
0

Just immediately off the bat, std::unique_ptr would take care of this for you, but if you want to do it yourself, then it would look something like this, keep in mind this is a minimal example.

RAII is a very important principle in C++, it basically means, allocate when you need it, but destroy whenever you're done using it.

So if a node is being pointed to, and you delete the node that points to it, then that node should also destroy the thing it points to since it has ownership over it.

class List {
    Node* first_node;

    ~List() {
        delete first_node;
    }

};

class Node {
    ~Node() {
        delete next; // will then in turn destroy the one it points to untill one is nullptr, deleting nullptr is well defined in C++ nowadays
    }
    Node* next;

};

Example with std::unique_ptr

class List {
    std::unique_ptr<Node> first_node;

    // default dtor
};

class Node {
    std::unique_ptr<Node> next;
    // default dtor

};
JohnkaS
  • 622
  • 8
  • 18
  • An alternative approach wasn't requested; that's not the question – Lightness Races in Orbit Dec 27 '19 at 21:01
  • 1
    @Maestro, it's a good property. If you want to copy a list, you don't really want to copy pointers. – Evg Dec 27 '19 at 21:04
  • @Evg: I know using smart pointers is safer, quicker and less error-prone but for some educational reason we should practice more. – Maestro Dec 27 '19 at 21:06
  • If you want to copy a linked list then you shouldn't copy the pointers, you should copy the content, so just implement a custom copy ctor that traverses the list and creates a new one, I actually believe that the Node class shouldn't be copied, but the List itself should, so that should handle it for you – JohnkaS Dec 28 '19 at 15:40