Smart pointers are only intended to model ownership of a given resource. Every resource has at least one owner; those owner(s) are the objects potentially responsible for releasing that resource. As the names suggest,
shared_ptr
models ownership shared between multiple owners, and
unique_ptr
models sole ownership. In this model, plain pointer types (raw pointers) do not own the resource they point to.
Your current design doesn't save you anything (you still need the to follow the rule of three) and the code is more complicated as a result. This is because the head and tail nodes don't own the next nodes in the list, meaning that those intermediate nodes will not be copied nor released by default. One way to avoid that might involve making the next pointer (
node::next
) a smart pointer -- a
unique_ptr
, preferably.
This suggests a singly-linked structure which looks something like this:
1 2 3 4 5 6
|
class list {
struct node { std::unique_ptr<node> next; /* etc. */ };
std::unique_ptr<node> head; // smart pointers are empty by default
node* tail = nullptr; // CppCoreGuidelines C.48 ( https://goo.gl/wLyadq )
// etc.
};
|
unique_ptr
is used because there is no shared ownership at all. The above structure implies that the container owns the head of the list, and each node in the list owns the next node. The tail is there only to make insertion at the end easier, and so the tail is not an owning pointer.
This still isn't a perfect structure for two main reasons -- one subtle, one not so much. The not-so-subtle error is a semantic one: according to the ownership semantics of smart pointers, every node owns the following one, and the list itself only owns the last node by proxy through an arbitrary number of other nodes. This is probably a mistake: this container is not intrusive, so the container is directly responsible for each node.
The more subtle problem is that this structure is prone to stack-overflow through recursive destruction of the entire ownership list -- when node
n
is destroyed,
n->next
will be destroyed recursively. This process consumes linear stack space in the length of the list, which is serious enough to disqualify the design in many circumstances.