Generic linked list in c++
I have been struggling for too long a time now with a rather simple question about how to create a generic linked list in c++. The list should be able contain several types of structs, but each list will only contain one type of struct. The problem arises when I want to implement the getNode() function [see below], because then I have to specify which of the structs it should return. I have tried to substitute the structs with classes, where the getNode function returns a base class that is inherited by all the other classes, but it still does not do the trick, since the compiler does not allow the getNode function to return anything but the base class then.
So here is some code snippet:
typedef struct struct1
{
int param1;
(...)
} struct1;
typedef struct struct2
{
double param1;
(...)
} struct2;
typedef struct node
{
struct1 data;
node* link;
} node;
class LinkedList
{
public:
node *first;
int nbrOfNodes;
LinkedList();
void addNode(struct1);
struct1 getNode();
bool isEmpty();
};
LinkedList::LinkedList()
{
first = NULL;
nbrOfNodes = 0;
}
void LinkedList::addNode(struct1 newData)
{
if (nbrOfNodes == 0)
{
first = new node;
first->data = newData;
}
else
{
node *it = first;
for (int i = 0; i < nbrOfNodes; i++)
{
it = it->link;
}
node *newNode = new node;
newNode->data = newData;
it->link = newNode;
}
nbrOfNodes++;
}
bool LinkedList::isEmpty()
{
return !nbrOfNodes;
}
struct1 LinkedList::getNode()
{
param1 return开发者_StackOverflowData = first->data;
node* deleteNode = first;
nbrOfNodes--;
if (nbrOfNodes)
first = deleteNode->link;
delete deleteNode;
return returnData;
}
So the question, put in one sentence, is as follows: How do I adjust the above linked list class so that it can also be used for struct2, without having to create a new almost identical list class for struct2 objects? As I said above, each instance of LinkedList will only deal with either struct1 or struct2. Grateful for hints or help
There is already a generic link list available in C++, std::list. It will definitely be more efficient & should suffice for your usage.
If you still want to create your own generic link list You should consider using templates and create a template implmentation of link list.
In c, where templates are not available the data node is stored in the form of a void*
pointer. It takes advantage of the fact that a void pointer can point to any generic data type, You might consider that approach as well.
Basic tempaltes are easy.
Just declare the class as template with a templated type variable.
Now everywhere you have the declare type which you want to be generic, in the class, replace the explicit type name with the templated variable name.
For example, in your code, you want struct1 to be generic, so we replace it with T:
template<class T>
class LinkedList {
public:
node *first;
int nbrOfNodes; LinkedList();
void addNode(T);
T getNode();
bool isEmpty();
};
The STL source would be a piece of code to study.
You could also try https://github.com/simonask/ftl/blob/master/list.hpp
Both use templates, which should be understood to be able to make any generic classes.
Here is a generic implementation without using STL. You can create a templated class with a generic type and instantiate the singly_linked_list.
namespace api
{
template <typename T>
class i_list
{
public:
i_list() = default;
virtual ~i_list(){}
/* Add to front*/
virtual int push_front(T t_value) = 0;
/* Return the front item*/
virtual T top_front() = 0;
/* Remove and return the front elenment */
virtual T pop_front() = 0;
/* Add to the back */
virtual int push_back(T t_value) = 0;
/* Returns the back item */
virtual T top_back() = 0;
/* Removes and returns the back item */
virtual T pop_back() = 0;
/* Is key in the list */
virtual bool find(T t_value) = 0;
/* Remove the key from the list */
virtual int erase(T t_value) = 0;
/* Erases the entire list */
virtual void erase_all() = 0;
/* Is the list empty */
virtual bool empty() = 0;
/* return the size of the list */
virtual size_t size() = 0;
};
}
namespace list
{
template<typename T>
struct node
{
T m_data;
node* m_next;
node* m_jump;
int m_order;
node(T t_data) : m_data(t_data), m_next(nullptr) , m_jump(nullptr), m_order(-1){}
};
template<typename T>
class singly_linked_list : public api::i_list<T>
{
public:
singly_linked_list() : m_head(nullptr), m_size(0){}
~singly_linked_list() {erase_all();}
virtual int push_front(T t_value) override;
virtual T top_front() override;
virtual T pop_front() override;
virtual int push_back(T t_value) override;
virtual T top_back() override;
virtual T pop_back() override;
virtual bool find(T t_value) override;
virtual int erase(T t_value) override;
virtual void erase_all() override;
virtual bool empty() override;
virtual size_t size() override;
template<typename U>
friend node<U>* get_head(singly_linked_list<U>& t_list);
private:
node<T>* get_last_node();
node<T>* get_node_until(size_t t_index);
node<T>* find_node(T t_value);
node<T>* get_node_pointer(size_t t_position);
int find_node_index(T t_value);
singly_linked_list<T> get_all_addresses();
node<T>* m_head = nullptr;
size_t m_size;
};
/* O(1) */
template<typename T>
inline int singly_linked_list<T>::push_front(T t_value)
{
node<T>* new_node = new node<T>(t_value);
new_node->m_next = m_head;
m_head = new_node;
m_size++;
return 0;
}
/* O(1) */
template<typename T>
inline T singly_linked_list<T>::top_front()
{
if (empty())
{
std::cout << " list is empty " << std::endl;
return T();
}
return m_head->m_data;
}
/* O(1) */
template<typename T>
inline T singly_linked_list<T>::pop_front()
{
if (empty())
{
std::cout << " list is empty " << std::endl;
return T();
}
/* Value to be returned */
T value = m_head->m_data;
node<T>* next_node = m_head->m_next;
delete(m_head);
m_head = next_node;
m_size--;
return value;
}
/* O(N) */
template<typename T>
inline int singly_linked_list<T>::push_back(T t_value)
{
node<T>* new_node = new node<T>(t_value);
if (empty())
{
m_head = new_node;
m_size++;
return 0;
}
get_last_node()->m_next = new_node;
m_size++;
return 0;
}
/* O(N) */
template<typename T>
inline T singly_linked_list<T>::top_back()
{
if (empty())
{
std::cout << " list is empty " << std::endl;
return T();
}
return get_last_node()->m_data;
}
/* O(N) */
template<typename T>
inline T singly_linked_list<T>::pop_back()
{
T value;
if (empty())
{
std::cout << " list is empty " << std::endl;
return T();
}
if (size() == 1)
{
value = m_head->m_data;
delete(m_head);
m_head = nullptr;
m_size = 0;
return value;
}
node<T>* last_node = get_last_node();
node<T>* last_node_before = get_node_until(size() -1);
value = last_node->m_data;
delete(last_node);
last_node_before->m_next = nullptr;
m_size--;
return value;
}
/* O(N) - Worst case */
template<typename T>
inline bool singly_linked_list<T>::find(T t_value)
{
return find_node(t_value) != nullptr;
}
/* O(N) - Worst case */
template<typename T>
inline int singly_linked_list<T>::erase(T t_value)
{
/* Node with t_value deos not exists */
if (empty())
return -1;
if (size() == 1)
{
delete(m_head);
m_head = nullptr;
m_size = 0;
return 0;
}
int index = find_node_index(t_value);
if (index == -1)
return index;
node<T>* node_to_erase = get_node_until(index + 1);
node<T>* before_node_to_erase = get_node_until(index);
node<T>* after_node_to_erase = get_node_until(index + 2);
before_node_to_erase->m_next = after_node_to_erase;
delete(node_to_erase);
m_size--;
return 0;
}
/* O(N) - Worst case */
template<typename T>
inline void singly_linked_list<T>::erase_all()
{
while(m_head != nullptr)
{
node<T>* next_node = m_head->m_next;
delete(m_head);
m_size--;
m_head = next_node;
}
}
/* O(1) */
template<typename T>
inline bool singly_linked_list<T>::empty()
{
return (m_head == nullptr);
}
/* O(1) */
template<typename T>
inline size_t singly_linked_list<T>::size()
{
return m_size;
}
template<typename T>
inline node<T>* singly_linked_list<T>::get_last_node()
{
node<T>* start_node = m_head;
node<T>* last_node = nullptr;
/* Traverse until the end */
while (start_node != nullptr)
{
last_node = start_node;
start_node = start_node->m_next;
}
return last_node;
}
template<typename T>
inline node<T>* singly_linked_list<T>::get_node_until(size_t t_index)
{
node<T>* start_node = m_head;
node<T>* until_node = nullptr;
/* Traverse until the a node before last node */
size_t index(1);
while (start_node != nullptr)
{
until_node = start_node;
if (index == t_index)
{
return until_node;
}
start_node = start_node->m_next;
index++;
}
return nullptr;
}
template<typename T>
inline node<T>* singly_linked_list<T>::find_node(T t_value)
{
node<T>* start_node = m_head;
/* Traverse until the end */
while (start_node != nullptr)
{
if (t_value == start_node->m_data)
return start_node;
start_node = start_node->m_next;
}
return nullptr;
}
template<typename T>
inline int singly_linked_list<T>::find_node_index(T t_value)
{
node<T>* start_node = m_head;
/* Traverse until the end */
int index(0);
while (start_node != nullptr)
{
if (t_value == start_node->m_data)
return index;
start_node = start_node->m_next;
index++;
}
/* t_value not found*/
return -1;
}
/* Returns the address of the specified node position
form the linke list chain */
template<typename T>
node<T>* singly_linked_list<T>::get_node_pointer(size_t t_position)
{
auto start_node{m_head};
/* Traverse until the end */
size_t i(0);
while (start_node != nullptr)
{
if(i == t_position)
return start_node;
start_node = start_node->m_next;
i++;
}
return nullptr;
}
template<typename U>
node<U>* get_head(singly_linked_list<U>& t_list)
{
return t_list.m_head;
}
}
struct1 and struct2 have different size in bytes, so sizeof(struct1) != sizeof(struct2). Returning the struct from function requires copying it, so c++ requires you to specify the correct type for it so that correct amount of bytes can be copied. To start correct this problem you need to think in the very low level:
struct GenericStruct {
void *ptr;
size_t size;
type_info t;
};
struct1 extract_struct1(GenericStruct &s);
struct2 extract_struct2(GenericStruct &s)
{
if (s.size != sizeof(struct2)) throw -1;
if (s.t != typeid(struct2)) throw -1;
struct2 *s2 = (struct2*)s.ptr;
return *s2;
}
GenericStruct make_generic(const struct1 &ss)
{
GenericStruct s;
s.ptr = (void*)&ss;
s.size = sizeof(struct1);
s.t = typeid(struct1);
return s;
}
GenericStruct make_generic(const struct2 &ss);
the real problem is that these functions can fail on runtime, if the sizes or types do not match. The copying is obviously also needed:
GenericStruct Copy(const GenericStruct &s);
After these basic primitives exists, you can create a class which has copy constructor and assignment operator which uses these functions to implement proper generic struct support.
精彩评论