Suppose, I have a singly linked list and its basic building block is,
struct Node {
Data d;
Node *pNext;
// methods
~Node();
};
The head of the linked list is stored as,
Node *m_Head; // member of some class
When, I am done with the list, I will clean it by deleting each node as,
void Erase()
{
Node *pIter, *pTemp = m_Head;
while((pIter = pTemp) != 0)
{
pTemp = pIter->pNext;
delete pIter;
pIter = pTemp;
}
}
I thought, if I can simplify this. So I came up with an idea where I can clean this whole linked list with just a single instruction !
delete m_Head;
and destructor will look like:
Node::~Node() { delete this->pNext; }
Here my concern is, will it cause recursion (implicitly due to delete) ? If yes, then it's definitely a concern for bigger linked lists. Will compiler be able to help in any way for optimizing that ?
[Note: Not using any library facility like std::list or other.]
I think the question that you have to ask is, does each Node in the list own its pNext Node? If not, then it has no business deleting its pNext node in its destructor.
In most linked list implementations all the nodes are owned by the list, a node doesn't own all the nodes after it in the list. It makes more sense to keep the nodes as dumb (POD-structs) and let all of the logic reside in the list.
It's definitely a design "smell" that your node has a destructor but no copy constructor or copy assignment operator. I think this approach will cause more complexity when you come to code implementing insert, splice and erase single element functions as you will have to manually manage the pNext pointers in any case to avoid unintentional destruction of the entire tail of a list.
Of course: Only do this for learning purposes or when you are sure that your own List is really better for your use case
It depends. Possibly your compiler will detect a tail recursion and emit code that is conceptually equivalent to using a loop.
If not, then yes, it will recurse. Usually, some thousands of recursions should be possible on commodity boxes, if stack pressure is small (like in your case). However, there is no guarantee, and indeed, for really large lists, this can be a problem.
Also, I think that recursion is indeed not entirely appropriate for the concept of sibling nodes. A node hierarchy, like with quadtrees, cries for recursion, but I have a not so good time thinking in recursion (which forms a call hierarchy) when the list-concept is about sibling-nodes.
You may also consider the manual loop as a easy-to-achieve optimization over the recursion that will make your code more robust in a guaranteed way.
Btw, you could also should rip out the deletion of nodes into a holder class:
class List {
public:
~List() {
for-each-node
delete-node
}
private:
class Node {
Node *node_;
...
};
...
};
This is basically how the standard library's list is usually implemented. It makes the whole implementation easier to achieve and conceptually more correct (Nodes don't own their sibling logically)
Most compiler do tail call elimination in default setting. Some smarter one can convert non-tail calls to tail calls.
So, this method okay as long as you have some optimization turned on.
Raw pointers ? Manual calls to delete ?
Why not, simply:
struct Node {
Data d;
std::unique_ptr<Node> next;
};
Then you don't even have to worry about memory management at all, it's automatic!
Related
Can we implement a link list without using the head pointer means by using a simple variable of the head instead of the pointer of the head ?
Yes. If you are implementing a circular linked list with a sentinel node, the sentinel node can be the simple variable that also serves as the head.
Alternatively, you could use a std::optional instance to serve as the head.
In specific cases you could, but in general not. And why would you want to? Here are some reasons, I could think of now. Take for example this code:
template<class T>
class Node
{
private:
T value;
Node<T> *next;
};
class MyLinkedList
{
private:
bool isEmpty; // indicates wether the list is empty or not
Node head; // Head as member
};
But there are several major flaws with this code:
You would always need to care about isEmpty when adding or deleting, or doing anything with the list
You can't initialize head if T has no default constructor
When deleting the last element you have to call the destructor of object that technically remains in scope.
When deleting the last element and then deleting the empty list the destructor of Node::value will be called twice
Don't know if those are all reasons, but I think, just #2 is a big enough problem to not consider this.
Of course you could use std::optional, but that's just a pointer with a wrapper. which even works, without a default constructor, so could be an alternative. Alltough it would be used in the same way as a (smart) pointer, so it's not "a simple variable of the head".
I got curious about how pointers and deleting pointers worked in C++ so I set up an experiment. I made a very simple singly linked list and the following recursive function that deletes all nodes in a list:
void deleteList(Node *node) {
if (!node)
return;
deleteList(node->next);
cout << "deleting node " << node->data << endl;
delete node;
node = nullptr;
}
I suppose that this successfully deletes all nodes in a list. However, after calling this function in main, I check to see if the head node still exists:
List list;
// appending a bunch of numbers to the list...
list.deleteList(list.head);
if (list.head)
cout << true;
this will print 1 to the console, meaning that the head does indeed still exist. I would expect the head, and all other nodes after it, to be null and hence that the if condition fails, since setting pointers to null is the last thing I do in the recursive function. So why does the program report that the head still exists?
edit: changed List list(); to List list;
You freed the memory, but the assignment to nullptr only affected the copy of the pointer passed to the function, not the original pointer in the caller.
If you declared the function as receiving the pointer by reference:
void deleteList(Node *&node) {
then the assignment of node = nullptr; would affect the caller as well.
Mind you, since you tagged this C++11, it's usually much simpler to just define the linked list as a series of std::unique_ptr<Node>s in the forward direction (raw pointers in the reverse direction if it's bidirectional to avoid reference cycles), so you can avoid the need for a special deleter function, and just set the head pointer to nullptr and let C++ do the work of cascading the deletion.
Edit: There is a flaw to letting std::unique_ptr do the work; as pointed out in the comments, this means the list size is effectively limited by the stack, and too large lists will cause stack overflow when you delete them. So explicitly clearing one by one (the simplest approach being to implement popping properly, and have clearing simply be popping until head is converted to a nullptr by the popleft method) would be safer. I left the original suggestion in place for posterity, so this explanation makes sense.
This is for a homework assignment in a cpp course and I'm looking for some best practises.
I've got a rather complex class that I would like to store in a linkedlist class. I must implement my own, including a node class. I need to only store this single relatively complex type I'm working on.
Should I make the node's data type a reference to the complex type, or should I make it a pointer to it instead?
Option 1:
class Node
{
public:
Node* prev;
Node* next;
ComplexType *data;
}
Option 2:
class Node
{
public:
Node* prev;
Node* next;
ComplexType &data;
}
Assuming I will also need to handle the different cases of copying value when working with the reference solution. Which one is considered more correct? And why?
I would go for option 2. Your prev and next need to be pointers, since they can change. A nodes data shouldn't change in a list. Only its position in the list should change by altering the prev/next pointers. In my opinion its always the best if you can prevent using pointers where possible, but in this case your prev/next member should be pointers, since they can change.
As soon as a reference is initialised, it cannot be changed again to reference anything else. Therefore your list will be more useful/flexible if you stick with pointers.
Presumably you need the freedom to have null data, as well as changing it after construction. In this case, you would need pointers of some variety.
I'm trying to keep a global list of a particular (base) class's instances so that I can track them down by iterating through this global list at any time.
I believe the most proper way to address this is with an intrusive list. I have heard that one can encounter these creatures by digging into the Linux kernel, for example.
In the situation where I'm in, I don't really need such guarantees of performance, and using intrusive lists will complicate matters somewhat for me.
Here's what I've got so far to implement this concept of a class that knows about all of its instances.
class A {
static std::forward_list<A*> globallist;
std::forward_list<A*>::iterator listhandle;
public:
A() {
globallist.push_front(this);
listhandle = globallist.begin();
}
virtual ~A() {
globallist.erase_after(...); // problem
}
};
The problem is that there is no forward_list::erase(), and it really does not appear like saving globallist.before_begin() in the ctor would do me much good. I'm never supposed to dereference before_begin()'s iterator. Will it actually hold on to the position? If I save out before_begin's iterator, and then push_front() a new item, that iterator is probably still not capable of being dereferenced, but will it be serviceable for sending to erase_after()?
forward_list is a singly linked list. To remove a node in the middle of that, you must have a pointer to previous node, somehow. For example, you could do something like this:
class A {
static std::forward_list<A*> globallist;
std::forward_list<A*>::iterator prev_node;
public:
A() {
A* old_head = globallist.front();
globallist.push_front(this);
prev_node = globallist.before_begin();
old_head->prev_node = globallist.begin();
}
};
The case of pushing the first element into an empty list, as well as the removal logic, are left as an exercise for the reader (when removing, copy your prev_node to the next node's prev_node).
Or, just use std::list and avoid all this trouble.
I'm implementing a vector type. I'm not troubled by the algorithms or the data structure at all but I am unsure about a remove method. for instance:
bool Remove(Node* node)
{
/* rearrange all the links and extract the node */
delete node;
}
where node is a pointer to the current node that we are at. But if I delete node then how do I prevent this from happening:
Node* currentNode = MoveToRandNode();
Remove(currentNode);
cout << currentNode->value;
If currentNode were a pointer to a pointer it would be easier but...it's not.
You could add another level of abstraction to your iterator (which now is a raw pointer)
If you do not handle raw pointers, but create some sort of iterator class instead of a pointer, it is possible to invalidate the iterator, and thus failing controlled if anyone tries to access the iterator after it has been removed.
class Iterator {
Node operator*() {
if (node) return *node;
else throw Something();}
private:
Node* node;
}
Of course this wrapping of a pointer will come at a cost of some overhead (checking the pointer on each deref). So you will have to decide how safe you want to play. Either document as suggested by others or wrap for safety.
Step back first. You need to define who "owns" the memory pointed to by the vector. Is it the vector itself, or the code that uses the vector? Once you define this, the answer will be easy - either Remove() method should always delete it or never.
Note that you've just scratched the surface of the possible bugs and you answer to "who owns it" will help with other possible issues like:
If you copy a vector, do you need to copy the items within it, or just the pointers (e.g. do a shallow or deep copy
When you destroy a vector, should you destroy the items within it?
When you insert an item, should you make a copy of the item, or does the vector take ownership of it?
well, you cannot do that, but some modifications to your code can improve safety.
Add ref
bool Remove(Node*& node)
{
/* rearrange all the links and extract the node */
delete node;
node = nullptr;
}
check for nullptr
if(currentNode)
cout << currentNode->value;
probably you need to try std::shared_ptr
This is similar to "iterator invalidation". E.g., if you have a std::list l and a std::list::iterator it pointing into that list, and you call l.erase(it), then the iterator it is invalidated -- i.e., if you use it in any way then you get undefined behavior.
So following that example, you should include in your documentation of the Remove method something along the lines: "the pointer node is invalidated, and may not be used or dereferenced after this method returns."
(Of course, you could also just use std::list, and not bother to re-invent the wheel.)
For more info on iterator invalidation, see: http://www.angelikalanger.com/Conferences/Slides/CppInvalidIterators-DevConnections-2002.pdf
In addition what innochenti wrote.
I think you have to decide what is expected/desired behavior of cout << currentNode->value;:
Error - (as innochenti wrote node = nullptr)
Default Value - create node devault_value (which has some default value for its value), and after delete node; do node=default_value