When you write a container, there are two concepts to consider: the container and what goes in the container. It can be much simpler to separate these concepts in your code--for example, into separate classes. I find it easier to reason about a container as a separate entity rather than holding on to one item in the container and manipulating the rest of the collection through that one item. It's the difference between trying to carry a chain by holding on to one link versus winding the whole chain around a spool and carrying the spool. Sure, the spool adds weight, but the savings in effort make it worth it.
To see how to make a spool, let's separate your class into two classes LinkedList
and LinkedListNode
.
struct LinkedListNode
{
int value;
LinkedListNode* next;
LinkedListNode(int new_value, LinkListNode* new_next) : value(new_value), next(new_next) {}
}
class LinkedList
{
private:
LinkedListNode* head;
LinkedListNode* tail;
public:
LinkedList() : head(nullptr), tail(nullptr), current(nullptr) {}
void add(int value);
LinkedListNode* find(int value);
}
At a cost of a single extra pointer, we can now have an add
method that does not need to search for the end of the list.
void LinkedList::add(int value)
{
if(head)
{
tail->next = new LinkedList(value, nullptr);
tail = tail->next;
}
else
{
head = new LinkedList(value, nullptr);
tail = head;
}
}
This also allows for other methods like find()
to be written as loops instead of recursion. If a LinkedList
has 1,000,000 elements and the compiler doesn't happen to optimize the recursive calls into loops, your program will crash with a stack overflow error. One way to do this would be
LinkedListNode* LinkedList::find(int value)
{
for(LinkedListNode* node = head; node; node = node->next)
{
if(node->value == value)
{
return node;
}
}
return nullptr;
}
I'm not saying recursion is bad. Sometimes a recursive algorithm is easier to reason about; sometimes a looping algorithm is easier. These separated classes give you the option to implement either as needed.