I'm trying to give my generic list class a reverse function. For some reason, my algorithm ain't workin' when I test it. I thought it made sense: swap the pointers to the first and last nodes of the list, then go through the list and for each node swap its pointers to the previous and next node.
Go easy on me, guys. I'm trying to get some practice with generic programming. Teach me the ways of a C++ purist.
Here's the swap function:
template <class T> void swap(T* a, T* b) {
T* tempPtr = a;
a = b;
b = tempPtr;
}
Here's the reverse function:
template <class T> void List<T>::reverse() {
if (size > 1) {
swap(firstNodePtr, lastNodePtr);
node* curNodePtr = firstNodePtr;
while (curNodePtr != NULL) {
swap(curNodePtr->prevNodePtr, curNodePtr->nextNodePtr);
curNodePtr = curNodePtr->nextNodePtr;
}
}
}
Here's the class, its members and prototypes for functions:
template <class T> class List {
public:
List();
~List();
void push_back(T);
void push_front(T);
T get_at(unsigned);
unsigned get_size();
void reverse();
private:
struct node {
T val;
node* prevNodePtr;
node* nextNodePtr;
};
node* firstNodePtr;
node* lastNodePtr;
unsigned size;
};
Your swap<T> function does not work: it exchanges pointers, which are copied by value into local variables of your function, which has no effect in the caller.
Dropping your own swap and replacing it with std::swap will fix this problem.
Since you pass the two pointers by value, the changes to a and b don't propagate out of the swap() function, making it a no-op.
One way to fix it is by passing the pointers by reference:
template <class T> void swap(T*& a, T*& b) {
Alternatively (and preferably) just use std::swap() instead of your own function.
If you exposed your node structure (or at least a bidirectional iterator type for your list), you could avoid the whole issue and just use std::reverse.
List<int> someList;
// fill with data
std::reverse(someList.begin(), someList.end()); // where begin returns a bidirectional iterator for the head, and end returns a bidirectional iterator for 1 element beyond the tail
Related
I've created a Node struct to be used in an implementation of binary search tree. It uses shared pointers to keep track of its children:
template <class T> struct Node;
template <class T>
using Node_ptr = std::shared_ptr<Node<T>>;
template <class T>
struct Node {
Node_ptr<T> left;
Node_ptr<T> right;
const T label;
...
};
Now, I'd like to have a function which given some subtree and a value will return either the node of that specific value or the place where such node should be located in future - find_node.
This is how it looks at the moment:
template <class T>
auto* find_node(Node_ptr<T>* node, const T& value) {
for (; *node && (*node)->label != value
; node = value < (*node)->label ? &(*node)->left : &(*node)->right);
return node;
}
Pretty bad. But it works:
template <class T>
class Binary_search_tree {
public:
// correctly inserts consecutive values
void insert(const T& value) {
if (auto* node = find_node(&root, value); !*node)
*node = std::make_shared<Node<T>>(value);
}
...
private:
Node_ptr<T> root;
...
};
I could rewrite find_node to use std::shared_ptr<Node_ptr<T>> instead of Node_ptr<T>* but it would look even worse. Or would it?
How should I handle such situations?
edit: As it's been pointed out, the function can be simplified a bit by taking a reference to starting node, and returning a reference to a node:
template <class T>
Node_ptr<T>& find_node(Node_ptr<T>& node_ref, const T& value) {
auto* node = &node_ref;
...
return *node;
}
Use of a raw pointer is suggested when you have to allow for passing a null pointer - not true in your example; or when you have to pass a non-integral value (true in your case). In the latter case one should still consider passing a reference rather than a raw pointer. This is a generic suggestion - so exceptions may exist.
Having noted that, you could still use a raw pointer in your function here rather safely by making find_node(...) a private function while keeping the insert(...) public. That is safe, since there is no chance of leaving the pointer dangling from inside insert(...).
Essentially we need to guard against two possibilities with raw pointers: #1. prematurely deleting the memory the pointer points to, #2. never deleting the memory that the pointer points to. Neither of this is possible inside your insert(...) function. So you're safe.
On a related note, you might consider having unique_pointer for your nodes when they are created and then converting them into shared pointers if they are to be shared by more child than one: std::move(...).
I was trying to implement generic linked list of objects in C++. But when I fetch the same object twice it gives me different results. I feel it is due to misuse of pointers. Please help me debug.
Here is the Node implementation. I have used pointers for templates since linked list shall contain user defined objects.
template <class T> class Node{
private:
T* value;
Node<T>* next;
public:
Node(T* v){value = v; next = NULL;}
Node(T* v, Node<T>* n){value = v; next = n;}
T* getElement(){return value;}
Node<T>* getNext(){return next;}
};
Here is the implementation for generic linked list.
template <class T> class LinkedList{
public:
Node<T>* head = NULL;
LinkedList(){}
LinkedList(T* value){
Node<T> node(value);
head = &node;
}
Node<T>* getHead(){
return head;
}
void add(T* value){
Node<T> node(value,head);
head = &node;
}
};
Main function:
When I call head of linked list, it gives me 2 different answers. In this code, Complex is a simple class to hold complex objects.
int main(){
Complex c1(1,2); Complex c2(3,4); Complex c3(5,6);
LinkedList<Complex> list(&c1);
list.add(&c2);
cout<<list.head->getElement()->i<<" "<<list.getHead()->getElement()->j<<endl;
cout<<list.head->getElement()->i<<" "<<list.getHead()->getElement()->j<<endl;
return 0;
}
Thanks in advance!!
In LinkedList(T* value) and void add(T* value), you are taking the address of a temporary with head = &node;. As soon as you are out of the scope of that function, head becomes a dangling pointer.
You need to create a new node on the heap so that its lifetime will extend beyond the scope of that function.
Node<T> node = new Node<T>(value);
Don't forget to delete all the nodes you have created in the destructor to avoid memory leaks, or even better, switch to smart pointers instead of raw pointers so the cleanup is done for you.
I've been working on updating my old templated linked list to be able to take a complex data type. But I have no idea how to make it be able to return the data element in the node class. Currently the code for my node class looks like this:
using namespace std;
#ifndef Node_A
#define Node_A
template <class T>
class Node
{
public:
Node();
~Node();
T getData();
Node* getNext();
void setData(T);
void setNext(Node*);
private:
Node *next;
T data;
};
template <class T>
Node<T>::Node()
{
next = NULL;
return;
}
template <class T>
Node<T>::~Node()
{
return;
}
template <class T>
T Node<T>::getData()
{
return data;
}
template <class T>
Node<T>* Node<T>::getNext()
{
return next;
}
template <class T>
void Node<T>::setData(T a)
{
data = a;
return;
}
template <class T>
void Node<T>::setNext(Node* a)
{
next = a;
return;
}
#endif
Now this works perfectly fine if the data type T is a primitive but if you use a non-primitive like say a struct it would give a runtime error. I presume because structs don't do operator overloading for = operator. Is there a simple way of fixing this without completely overhauling the class?
It's not about overloading the = operator, it's about implementing the assignment operator for the struct. If you do that, you won't need to change your Node class, unless I've missed something else.
The above assumes that you'll be making copies of the data inside the Node. Alternatively, you can pass the data by reference. In this case, you need to be careful that the data doesn't get deleted before the Node object is deleted, otherwise you'll get a crash when trying to access a deleted data object from your Node.
I have one semestral work (own double linked list) and our teacher want this definition of class DoubleList:
template <typename T> //just part of all methods
class DoubleList {
public:
DoubleList(void); //We HAVE TO follow this definitions
void AddFirst(const T &); //const!
T &AccessActual(void);
T RemoveFirst(void);
}
My question is, how can I define a node? AddFirst have const argument and other methods haven't. Data must be set in constructor and then they can't be changed. Is this task so limited or are here other ways to complete the task?
Here is my actual Node:
template <class U>
class Node{
Node<U> * next;
Node<U> * previous;
const U * data;
public:
Node(const U *data){ //
next = NULL;
previous = NULL;
this->data = data;
}
void SetNext(Node<U> *next) {
this->next = next;
}
Node<U> *GetNext(){ return next; }
void SetPrevious(Node<U> *previous) {
this->previous = previous;
}
Node<U> *GetPrevious(){ return previous; }
const U *GetData() { return data; }
};
In containers, it's usually better to have a copy of the data so change const U * data; to U data;
The Node constructor would be easier to use if it had this signature Node(const U& data). No pointers.
The GetData would also have to change. Return a reference. U& GetData().
It is dangerous to hold addresses of data items. If the user of the lists wants that functionality he can use a list that stored pointers (e.g. U=int*)
Your node class seems fine, although i would keep using template argument T instead of U, right now it is confusing.
Your AddFirst() method should simply create a new node and assign the correct next pointer to the new node and the correct prev pointer to the "old" first node and adjust the actual object? what does that refer to?
Your interface of nodes differs from this one returning a reference instead of a pointer. I find it quite strange that the AccessActual can always return an object, while when the list is empty this can be a nullptr??
example implementation:
void AddFirst(const T &)
{
Node<T>* newNode = new Node<T>(T);
Node<T>* current = &AccessActual(); // how can there be an actual when the list can be empty or is that impossible?
{
while( current.GetPrev() != nullptr )
{
current = *current.GetPrev();
}
current.SetPrev(newnode);
newnode->SetNext(current);
}
}
I don't get what Bjarne has on mind stating:
A list iterator must be something more complicated than a simple
pointer to an element because an element of a list in general does not
know where the next element of that list is. Thus, a list iterator
might be a pointer to a link
I know that list's node has pointers to the next and previous nodes. So how it "in general does not know"?
For contrast, let's consider an iterator for std::vector. Though you probably don't want to, you could basically implement it as a simple pointer:
template <class T>
class vector {
T *data;
size_t current_size;
size_t alloc_size;
public:
typedef T *iterator;
iterator begin() { return data; }
iterator end() { return data+current_size; }
// ...
};
and since the data in the vector is contiguous, when you do (for example) ++ on the iterator, it would do the right thing (get you to the next item in the vector).
With a linked list, it can't be that simple though -- if you used typedef to make iterator an alias for T *, it wouldn't work right. When you tried to do ++ on the iterator, it would just increment the pointer, instead of taking you to the next element in the linked list like it needs to.
You need to build some intelligence into the iterator class so operator++ (for one example) knows to "chase" the pointer instead of just incrementing.
template <class T>
class list {
class node {
T data;
node *next;
node *prev;
};
public:
class iterator {
node *pos;
public:
iterator operator++() {
return pos = pos->next;
}
iterator operator--() {
return pos = pos->prev;
}
};
};
Don't confuse list node with a list element. It might be a pointer, but to a node, which contains element and next/prev links. If it were just a pointer to the element, you wouldn't be able to move around with it. (Though it's unlikely to be just pointer, since it typically has to overload operator++ to fetch next pointer instead of incrementing itself).
This isn't true for intrusive lists, where node == element, but that's not std::list.
// illustrative
template <typename ElementType>
struct list_node {
ElementType element;
list_node *next, *prev;
};