I think there are a few points worth mentioning that I haven't seen in any replies yet.
struct node {
int data;
node *next;
};
As @Edward mentioned, this should almost certainly be a template. The next
pointer should almost certainly be a unique_ptr
instead of a raw pointer.
node *reverse(node *list) {
node *prev = NULL;
node *next;
while (list) {
next = list->next;
list->next = prev;
prev = list;
list = next;
}
return prev;
}
Although a couple of people mentioned exceptions, nobody mentioned the fact that this code isn't re-entrant. If an exception is thrown while the list is being reversed, the list will typically be left in a bad state--in particular, part of it may be orphaned. The reversal is done by traversing the list, and manipulating each node's pointer to point to the previous node instead of the next one. While this is being done, the list is basically broken into two pieces. An exception thrown in the middle of that process is likely to "lose" one of the pieces.
Likewise, making this thread safe (by almost any reasonable definition of the term) basically requires that the entire reversal process be treated as a single critical section. That is to say, no other thread can be allowed to read part of the list at any point in the reversal process. This is likely to be a serious problem if there is much contention for use of such a collection.
void show_list(node *list) {
while (list != NULL) {
std::cout << list->data << ", ";
list = list->next;
}
}
This displays a comma and space after the final item in the list. It would be better to provide access to the items in the list via iterators. With iterators, it would be trivial to use something like an infix_ostream_iterator to display the data more neatly.
node *list = NULL;
As @iavr showed, it's better to use nullptr
instead of NULL. While NULL
works perfectly well to produce a null pointer, it can also be used by accident in places that aren't related to pointers at all (because NULL
is simply a macro for the value 0
). By contrast, nullptr
can be assigned to any pointer, but not to other types such as integers.
Overall, this code should clearly be avoided for almost anything except its originally intended use (in-place reversal of a linked list).
Since @David K mentioned the possibility of using std::list
, and std::slist
, I'll point out yet one more possibility. If you honestly need to a linked list (you probably don't) and need to traverse in both directions, and you really can't afford a doubly-linked list like std::list
, there is one other possibility that may be worth considering: a doubly linked list that stores only one link per node.
Although thoroughly crufty in some ways, this structure is just clever enough to be worth mentioning in this context. To store both backward and forward links in only one link field, what we store in the node is actually the exclusive-or of the addresses of the previous and next nodes in the list. The parent linked-list typically stores a dummy node containing pointers to the first and last nodes in the list.
We can then use those pointers to recover the rest of the pointers. We have the address of node A, and the XOR of the addresses of node A and node B. Xoring those two together gives us the address of node B. That node, of course, contains the XOR of the addresses of node B and node C. Since we already have the address of node B, we can XOR it with the value from the node to get the address of node C (and so on for the remaining nodes in the list).
Conversely, if we start with the address of node Z and the XOR of the addresses of node Z and node Y, we can use exactly the same procedure to isolate the address of node Y. That, in turn, is enough to recover the address of node X, and so on through the list in reverse.
This obviously has its own set of strengths and weaknesses. The most obvious weakness is simple obscurity--virtually nobody will simply recognize this structure and understand how it works without at least a little study (and a few probably won't trust it even then). Less obviously, this technique is generally incompatible with automated (tracing-based) garbage collection. A garbage collector detects what memory is still accessible by traversing all accessible pointers. Without special knowledge about how to recover the actual pointers in the list, all but the first and last nodes of this list will appear inaccessible.
Nearly the sole real strength is what was stated up front: this lets you build a linked list that can be traversed in either direction while storing only (bits the same size as) one pointer per node. Since it can be traversed in either direction without modification, it avoids the reentrancy problems inherent in the original code.