C ++ Linked List Using Smart Pointers

I used only raw pointers for a linked list with patterns. For example, item data, Node<T>* head; , and when I insert node, one of the lines will be head = new Node<T>(data); .

However, now I need to use a smart pointer, and I'm not sure how to change it to use smart pointers. Is the participant data changed to shared_ptr<Node<T>> head; , and the other line will change to head = shared_ptr<Node<T>>( new <Node<T>>(data) ); ?

+5
source share
3 answers

You do not need to use a smart pointer for a linked list, because this statement does not make sense. You use non- smart pointers for low-level data structures. You use smart pointers for high-level programming logic.

As for the low-level data structures, you use the standard container class from the C ++ standard library, for example std::list [*], which in any case solves all your memory management problems, without internal smart pointers.

If you really need your own specialized / optimized custom container class, because the entire C ++ standard library is not suitable for your requirements, and you need a replacement for std::list , std::vector , std::unordered_map and other optimized, tested , documented and safe containers - which I highly doubt! -, then you still need to manage the memory manually, because a point of such a specialized class will almost certainly need methods such as memory pools, copying to write, or even garbage collection, all of which conflict with a typical smart pointer rather simplified deletion logic.

In the words of Herb Sutter :

Never use your own raw pointers and delete , except in rare cases when implementing your own low-level data structure (and even then that are well encapsulated inside the class border).

Something in these lines is also expressed in Sutter and Bjarne Stroustrup C ++ Herbs Basic Principles :

This problem cannot be solved (on a scale) by converting all owned pointers to unique_ptrs and shared_ptrs, in part because we need / use owning "raw pointers", as well as simple pointers to the implementation of our main resource it processes . For example, an ordinary vector implementation has one own pointer and two pointers without rights.

Writing a class of linked lists in C ++ with raw pointers can be a useful academic exercise. Writing a C ++ linked list class using smart pointers is a meaningless academic exercise. Using either of these two home-made things in production code is almost automatically a mistake.


[*] Or just std::vector , because due to the locality of the cache there will always be a better choice.

+6
source

There are basically two alternatives for setting up an extended list of smart pointers:

  • Using std::unique_ptr :

     template<typename T> struct Node { Node* _prev; std::unique_ptr<Node> _next; T data; }; std::unique_ptr<Node<T> > root; //inside list 

    That would be my first choice. The unique _next pointer _next care that there are no memory leaks, while _prev is a watch pointer. Copying and such things, however, must be defined and implemented manually.

  • Using shared_ptr :

     template<typename T> struct Node { std::weak_ptr<Node> _prev; //or as well Node* std::shared_ptr<Node> _next; T data; }; std::shared_ptr<Node<T> > root; //inside list 

    This is a safer alternative, but less effective than with a unique pointer. In addition, it can be copied by design.

In both cases, the idea is that one node owns the entire remaining list. Now that the node goes out of scope, there is no danger that the remaining list will become a memory leak, since the nodes are iteratively destroyed (starting from the last).

The _prev pointer is in both cases only an observation pointer: the task is not to keep the previous nodes alive, but only to provide a link to visit them. Node * (-note: a watch pointer means that you never do memory-related things like new , delete on a pointer) is usually sufficient for this.

If you need extra security, you can also use std::weak_ptr for this. it prevents things like

 std::shared_ptr<Node<T> > n; { list<T> li; //fill the list n = li.root->next->next; //let say that works for this example } n->_prev; //dangling pointer, the previous list does not exists anymore 

Using weak_ptr , you can lock() it and thus check if _prev is _prev .

+2
source

I would look at the std :: list interface, which is an implementation of C ++ related lists. It seems that you are incorrectly approaching the template of your Linked list. Ideally, your linked list should not care about ownership semantics (that is, whether it will be created using raw ptrs, smart pointers, or variables allocated on the stack). The following is an example of semantics of ownership with STL containers. However, there are better examples of STL and property rights from more reputable sources.

 #include <iostream> #include <list> #include <memory> using namespace std; int main() { // Unique ownership. unique_ptr<int> int_ptr = make_unique<int>(5); { // list of uniquely owned integers. list<unique_ptr<int>> list_unique_integers; // Transfer of ownership from my parent stack frame to the // unique_ptr list. list_unique_integers.push_back(move(int_ptr)); } // list is destroyed and the integers it owns. // Accessing the integer here is not a good idea. // cout << *int_ptr << endl; // You can make a new one though. int_ptr.reset(new int(6)); // Shared ownership. // Create a pointer we intend to share. shared_ptr<int> a_shared_int = make_shared<int>(5); { // A list that shares ownership of integers with anyone that has // copied the shared pointer. list<shared_ptr<int>> list_shared_integers; list_shared_integers.push_back(a_shared_int); // Editing and reading obviously works. const shared_ptr<int> a_ref_to_int = list_shared_integers.back(); (*a_ref_to_int)++; cout << *a_ref_to_int << endl; } // list_shared_integers goes out of scope, but the integer is not as a // "reference" to it still exists. // a_shared_int is still accessible. (*a_shared_int)++; cout << (*a_shared_int) << endl; } // now the integer is deallocated because the shared_ptr goes // out of scope. 

A good exercise to understand owning, allocating / freeing memory, and shared pointers is to make a tutorial in which you implement your own smart pointers. Then you will definitely understand how to use smart pointers, and you will have one of those xen moments where you will understand how almost everything in C ++ returns to RAII (resource ownership).

So, back to the essence of your question. If you want to stick to Nodes of type T, do not wrap the node with a smart pointer. The node destructor should remove the main raw pointer. The raw pointer can point to the smart pointer itself, indicated as T. When you call the destructor of the LinkedList class, it iterates through all the nodes using Node :: next and calls delete node; after getting a pointer to the next node.

You can create a list in which nodes are smart pointers ... but this is a very specialized linked list, which is probably called SharedLinkedList or UniqueLinkedList with very different semantic properties for creating an object, popping up, etc. As an example, a UniqueLinkedList will move a node in the return value when the value appears to the caller. To perform metaprogramming for this task, you will need to use partial specialization for different types of transmitted T. Example, something like:

 template<class T> struct LinkedList { Node<T> *head; }; // The very start of a LinkedList with shared ownership. In all your access // methods, etc... you will be returning copies of the appropriate pointer, // therefore creating another reference to the underlying data. template<class T> struct LinkedList<std::shared_ptr<T>> { shared_ptr<Node<T>> head; }; 

Now you are starting to implement your own STL! You can already see the potential problems mentioned in the comments on your question with this approach. If the nodes have shared_ptr next, this will call this node's shared destructor, which will call the next common node destructor, etc. (Stack overflow due to recursion possible). Therefore, I do not really like this approach.

0
source

Source: https://habr.com/ru/post/1247299/


All Articles