3

I'm a little confused about implementing a doubly linked list where the data in the list are pointers.

The private part of my linked list class looks like:

 private:
    struct node {
      node* next;
      node* prev;
      T* o;
    };
    node* first; // The pointer to the first node (NULL if none)
    node* last;  // The pointer to the last node (NULL if none)
    unsigned int size_;

As you can see, the list is full of pointers to objects rather than just plain old objects, which makes it a little more confusing to me.

The following is the description in the spec:

Note that while this list is templated across the contained type, T, it inserts and removes only pointers to T, not instances of T. This ensures that the Dlist implementation knows that it owns inserted objects, it is responsible for copying them if the list is copied, and it must destroy them if the list is destroyed.

Here is my current implementation of insertFront(T* o):

void Dlist::insertFront(T* o) {
  node* insert = new node();
  insert->o = new T(*o); 
  insert->next = first;
  insert->prev = last;
  first = insert;
}

This seems wrong though. What if T doesn't have a copy constructor? And how does this ensure sole ownership of the object in the list?

Could I just do:

insert->o = o;

It seems like this is not safe, because if you had:

Object* item = new Object();
dlist.insertFront(item);
delete item;

Then the item would be also be destroyed for the list. Is this correct? Is my understanding off anywhere?

Thanks for reading.

Note: While this looks like homework, it is not. I am actually a java dev just brushing up my pointer skills by doing an old school project.

Slims
  • 864
  • 2
  • 13
  • 31

1 Answers1

4

When you have a container of pointers, you have one of the two following usage scenarios:

  1. A pointer is given to the container and the container takes responsibility for deleting the pointer when the containing structure is deleted.

  2. A pointer is given to the container but owned by the caller. The caller takes responsibility for deleting the pointer when it is no longer needed.

Number 1 above is quite straight-forward.

In the case of number 2, it is expected that the owner of the container (presumably also the caller) will remove the item from the container prior to deleting the item.

I have purposely left out a third option, which is actually the option you took in your first code example. That is to allocate a new item and copy it. The reason I left it out is because the caller can do that.

The other reason for leaving it out is that you may want a container that can take non-pointer types. Requiring it to be a pointer by always using T* instead of T may not be as flexible as you want. There are times when you should force it to be a pointer, but I can't think of any use (off the top of my head) for doing this for a container.

If you allow the user to declare Dlist<MyClass*> instead of Dlist<MyClass> then the owner of that list is implicitly aware that it is using pointers and this forces them to assume scenario Number 2 from above.

Anyway, here are your examples with some commentary:


1. Do not allocate a new T item unless you have a very good reason. That reason may simply be encapsulation. Although I mentioned above that you shouldn't do this, there are times when you may want to. If there is no copy constructor, then your class is probably plain-old-data. If copying is non-trivial, you should follow the Rule of Three.

void Dlist::insertFront(T* o) {
  node* insert = new node();
  insert->o = new T(*o);         //<-- Follow rule of three
  insert->next = first;
  insert->prev = last;
  first = insert;
}

2. This is what you would normally do

insert->o = o;

3. You must not delete your item after inserting. Either pass ownership to your container, or delete the item when neither you nor the container requires it anymore.

Object* item = new Object();
dlist.insertFront(item);
delete item;                     //<-- The item in the list is now invalid
Community
  • 1
  • 1
paddy
  • 60,864
  • 6
  • 61
  • 103
  • In (3), when you say 'pass the ownership to your container', do you mean just setting item = null? – Slims Nov 23 '12 at 01:25
  • 1
    I mean just don't delete it. You can set it to NULL if you really want, but it's just a pointer (ie a number) stored in a variable. By 'pass ownership' I mean ONLY if your container is defined with ownership semantics: *ie* it will always delete contained pointers when finished. In that case the caller never has to delete or remember the pointer - the container does all of that. I generally prefer to supply a pointer type in my template (rather than an implicit pointer type) because there can never be any confusion over whether the container owns pointers. – paddy Nov 23 '12 at 01:30
  • Cool. Thanks, paddy, this was all very helpful. – Slims Nov 23 '12 at 01:33
  • Err.. Sorry if that's confusing. There is no physical difference between allowing the container to own your pointer or owning it yourself. It's a conceptual difference. You can store the pointer value in as many places as you want - but it should only ever be deleted once, and should never be used after it's deleted. – paddy Nov 23 '12 at 01:34