-1

So it's been a while since I've done any c++ coding and I was just wondering which variables in a basic linked list should be deleted in the destructor and unfortunately I can't consult my c++ handbook at the moment regarding the matter. The linked list class looks as follows:

#include <string>
#include <vector>

class Node
{
    Node *next;
    string sName;
    vector<char> cvStuff;

    Node(string _s, int _i)
    {
        next = nullptr;
        sName = _s;

        for (int i = 0; i < _i; i++)
        {
            cvStuff.insert(cvStuff.end(), '_');
        }
    }

    ~Node()
    {
         //since sName is assigned during runtime do I delete?
         //same for cvStuff?
    }
};

I'm also curious, if in the destructor I call

delete next;

will that go to the next node of the linked list and delete that node and thus kind of recursively delete the entire list from that point? Also, if that is the case and I choose for some reason to implement that, would I have to check if next is nullptr before deleting it or would it not make a difference?

Thank you.

Franken Steak
  • 60
  • 2
  • 9
  • 1
    This tries to be both a ListNode and a List. Who owns the list? Do you share tails of lists if they are the same? Do you plan to delete elements, or just the entire list? – lorro Sep 02 '16 at 20:04
  • @lorro, yes this is technically a node class and has been adjusted accordingly. Define share tails? Also, delete entire list. – Franken Steak Sep 02 '16 at 20:40
  • in this case, don't delete! Otherwise you won't be able to write a quick erase that'd erase any given element of the list. Sharing tails of forward-linked list means that, given you know that two lists should have the same elements on the back, you share the nodes. Very useful with lists that are once built and become const. – lorro Sep 02 '16 at 20:51

4 Answers4

1

Ideally, nothing: use smart pointers: std::unique_ptr<>, std::smart_ptr<>, boost::scoped_ptr<>, etc..

Otherwise, you delete what's an owning native pointer. Is next owning?

  • Do you plan to delete something in the middle of the list? If yes, you can't delete in the destructor.
  • Do you plan to share tails? If yes, you need reference-counted smart pointers.

It's okay to delete nullptr (does nothing). In the example, you shouldn't delete sName and cvStuff as those are scoped, thus destroyed automatically.

Also, if this is going to be a list that can grow large, you might want to destroy & deallocate *next manually. This is because you don't want to run out of stack space by recursion.

Furthermore, I suggest separating this to List, meaning the data structure and ListNode, meaning an element. Your questions actually show this ambiguity, that you don't know whether you're deleting the ListNode or the List in the destructor. Separating them solves this.

lorro
  • 10,687
  • 23
  • 36
  • I'm not sure whether or not next is owning. 1: thinking about functionality I'll never only delete a single element but would delete the entire list which is why I wanted to know about the whole "recursive destructor" part. 2: I don't know what it means to share tails, is it when there are multiple pointers pointing to a single object? 3: the list will never be larger than six, but regarding the manual destruction due to running out of stack space I assume you mean manually as in deleting the nodes iteratively in the class that contains these nodes? 4: yes, the class should be called "Node" – Franken Steak Sep 02 '16 at 20:30
  • 1. if you never want to erase, it's up to you. I'd strongly consider unique_ptr<> even then - that way, you don't have to make this choice. 2. exactly - if sharing, use shared_ptr<> or sg. similar 3. in which case, forget list and store a small vector of element ptrs (or even elements, if you can). apart from that, yes, I meant iterating. 4. ok; still then, I'd suggest having a `List` that deletes and a `Node` that doesn't (least surprise) - but the choice is open. – lorro Sep 02 '16 at 20:57
1

An object with automatic lifetime has it's destructor called when it goes out of scope:

{  // scope
    std::string s;
}  // end scope -> s.~string()

A dynamic object (allocated with new) does not have it's destructor called unless delete is called on it.

For a member variable, the scope is the lifetime of the object.

struct S {
    std::string str_;
    char* p_;
};

int main() {  // scope
    {  // scope
        S s;
    }  // end scope -> s.~S() -> str_.~string()
}

Note in the above that nothing special happens to p_: it's a pointer which is a simple scalar type, so the code does nothing automatic to it.

So in your list class the only thing you have to worry about is your next member: you need to decide whether it is an "owning" pointer or not. If it is an "owning" pointer then you must call delete on the object in your destructor.

Alternatively, you can leverage 'RAII' (resource aquisition is initialization) and use an object to wrap the pointer and provide a destructor that will invoke delete for you:

{  // scope
    std::unique_ptr<Node> ptr = std::make_unique<Node>(args);
}  // end scope -> ptr.~unique_ptr() -> delete -> ~Node()

unique_ptr is a purely owning pointer, the other alternative might be shared_ptr which uses ref-counting so that the underlying object is only deleted when you don't have any remaining shared_ptrs to the object.

You would consider your next pointer to be a non-owning pointer if, say, you have kept the actual addresses of the Nodes somewhere else:

std::vector<Node> nodes;
populate(nodes);

list.insert(&nodes[0]);
list.insert(&nodes[1]);
// ...

in the above case the vector owns the nodes and you definitely should not be calling delete in the Node destructor, because the Nodes aren't yours to delete.

list.insert(new Node(0));
list.insert(new Node(1));

here, the list/Nodes are the only things that have pointers to the nodes, so in this use case we need Node::~Node to call delete or we have a leak.

kfsone
  • 23,617
  • 2
  • 42
  • 74
0

Basically you should just

delete next 

and that's all you should do:

  • The string and vector objects have their own destructors, and since this object is being destructed, theirs will be called.

  • Deleting a null pointer is not a problem, so you don't even have to check for that.

  • If next is not a null pointer, it will keep on calling the destructors of the next nodes, on and on, as needed.

Just delete it and that's all, then.

Community
  • 1
  • 1
Ami Tavory
  • 74,578
  • 11
  • 141
  • 185
  • 1
    Hmm, it's true for const non-tail-sharing lists only. For non-const lists, you cannot delete next: that'd disallow erasing an element O(1). For tail-sharing lists, it's even worse: you'll end up deleting all other elements. – lorro Sep 02 '16 at 20:02
  • 1
    @lorro For recursive data structures, you really should use reference-counting smart pointers like `shared_ptr`; anything else would be a nightmare. Node-sharing data structures being much much rarer, perhaps you could mention this in the question next time. – Ami Tavory Sep 02 '16 at 20:06
  • @AmiTavory: lorro is not the question asker – Mooing Duck Sep 02 '16 at 20:10
  • @Ami: not my question :), but whenever we implemented (non-school) lists, they were at least const tail-sharing: otherwise just use `std::list<>`. Also note, you can't even delete if you're non-sharing but allow erasing an element in the middle (in OP's solution). – lorro Sep 02 '16 at 20:10
  • @lorro Oh, sorry for the mixup (I don't think any harm was done, but sorry anyway). Did you actually come across this often in C++? It's very common in Haskell, Scheme, etc., but I haven't personally ever seen it in production. Nevertheless, `shared_ptr`-based management seems the way to go there. – Ami Tavory Sep 02 '16 at 20:13
  • @AmiTavory: In finance, payouts of an instrument is a discrete time to value function. You either share the prefix or postfix where you can to minimize equations to solve in any fitting (discount comes to mind). tail-shared list is a viable solution. This can be generalized to a class of sparse linear equation system solvers/minimizers. Implementing zip (as archiver, GIF images, etc.) is basically having tail-shared lists (or the reverse, as you look at it). Also I've heard parser writing team used it for something. I agree that `shared_ptr<>` is the most generic (beware of loops in count :) ) – lorro Sep 02 '16 at 21:20
0

Your class destructor will be called (it is empty), then the member objects destructors are called.

If member is not an object, no destructor is called.

In your example:

- List *next: pointer on List: no destructor called
- string sName: string object: destructor called
- vector<char> cvStuff: vector object: destructor called

Good news: you have nothing to do. Destructor declaration is not even useful here.

If you delete next in your destructor, then deleting an item will delete all other items of your list: not very useful.

(and your List object should rather be called Node). The List is the chained result of all your nodes, held by the first node you created.

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219