-
Notifications
You must be signed in to change notification settings - Fork 6
Description
Chapters 8 and 9 introduce list_pool
and list_pool_iterator
. A few things confused me while I read those chapters. I've listed them here in case they are useful:
Examples of using the list_pool
class
list_pool
has an unusual API compared to what we're used to from the STL. It was not immediately obvious to me how I was supposed to use it. I eventually discovered test_merge_sort.cpp
which generates a list via the push methods on the iterator. But there are no examples of how to use the most important member functions of list_pool
. And I wasn't expecting to need to read test_merge_sort.cpp
when I was only on Chapter 8.
Perhaps it would help to give some brief examples in the chapters themselves? It's difficult to come up with concise examples, as the API seems to be designed to be used mostly via algorithms like generate_list
. Here's my best stab at code using the list_pool
API directly.
Chapter 8
This uses the queue operations. Without them I guess you have to keep calling allocate
, which is probably a bit too low level.
list_pool<char> pool; list_pool<char>::pair_type q = pool.empty_queue(); // push_xxx does mutate the pool, but not the queue. So you must // remember to update the queue object yourself. q = pool.push_back(q, 'c'); q = pool.push_back(q, 'd'); q = pool.push_back(q, 'e'); q = pool.push_front(q, 'b'); q = pool.push_front(q, 'a'); q = pool.push_back(q, 'f'); // If you want to print the list without using the list_iterator then I think this is // the only way? But at least it demonstrates how to use `value` and `next`. list_pool<char>::list_type node = q.first; while (node != pool.end()) { std::cout << pool.value(node) << " "; node = pool.next(node); } free_list(pool, q.first);
Chapter 9
Using iterators to populate and print a list. Using push_back
is more challenging, as you need to increment the insertion point each time, which gets messy. It's much neater to use something like the generate_list
function from list_algorithm.h
.
list_pool<char> pool; list_pool<char>::iterator nil = list_pool<char>::iterator(pool); list_pool<char>::iterator list = nil; push_front(list, 'd'); push_front(list, 'c'); push_front(list, 'b'); push_front(list, 'a'); std::copy(list, nil, std::ostream_iterator<char>(std::cout, " "));
Why there is no pop_back
The list_pool
API contains push_front
and push_back
, but only pop_front
and not pop_back
. That lack of symmetry seemed odd until I realised that you can't implement pop_back
efficiently for a singly-linked list. A note to that effect might be worthwhile somewhere in Chapter 8?
pop_front
does not free
the removed elements
I'm still not sure whether that's a bug or whether it is by design. I suppose it means you end up with two lists sharing a common tail sequence, assuming you kept hold of your original head node. This isn't really something actionable in the write-up, but it is a thing that still bothers me.
Singly Linked List Iterator concept
I find this concept unsettling. I've not come across iterators that mutate the structure of the underlying data structure before (have I?). These iterators do a lot more than act as 'coordinates'!
The fact that the push_front
and push_back
friend functions inside list_pool_iterator.h
return void
even though they allocate new nodes is also odd. As it stands, push_front
mutates the iterator, but push_back
does not. I'd think it would be more useful for both of them to return an iterator to the newly created node or something. That would make them a little easier to hold correctly, and make the generate_list
function easier to write.
But who am I to doubt Alex Stepanov? Would be interested to hear your thoughts on why it's designed this way.