I am fairly new to C++ and have been trying to make a doubly-linked-list class that imitates std::list (though not all of it). I'd appreciate if someone can look at my code and let me know if I have missed anything important, and if everything seems to be implemented correctly.
Note:the end node is implemented as one past the last element.
Here is the interface:
#ifndef my_list_h
#define my_list_h
#include <cstdlib>
// forward declarations
template<class T> class my_list;
template<class Value,class Pointer,class Reference> class
my_bidirectional_iterator;
template<class T> class my_list_iterator;
template<class T> class my_list_const_iterator;
template<class T> class node {
node(const T& t = T()):data(t),next(0),prev(0) {}
T data;
node* next;
node* prev;
friend class my_list<T>;
friend class my_bidirectional_iterator<node<T>,T*,T&>;
friend class my_bidirectional_iterator<node<T>,const T*,const T&>;
};
template<class Value,class Pointer,class Reference> class my_bidirectional_iterator {
public:
// increment and decrement operators
my_bidirectional_iterator operator++();
my_bidirectional_iterator operator++(int);
my_bidirectional_iterator operator--();
my_bidirectional_iterator operator--(int);
// bool comparison iterators
bool operator==(const my_bidirectional_iterator& other) const {return pos_==other.pos_;}
bool operator!=(const my_bidirectional_iterator& other) const {return pos_!=other.pos_;}
// member access
Reference operator*() const {return pos_->data;}
Pointer operator->() const {return &(pos_->data);}
private:
explicit my_bidirectional_iterator(Value* p=0):pos_(p) {}
Value* pos_;
template<class U> friend class my_list_iterator;
template<class U> friend class my_const_list_iterator;
template<class U> friend class my_list;
};
template<class T> class my_list_iterator: public my_bidirectional_iterator<node<T>,T*,T&> {
using my_bidirectional_iterator<node<T>,T*,T&>::my_bidirectional_iterator;
friend class my_list_const_iterator<T>;
public:
operator my_list_const_iterator<T>() {return my_list_const_iterator<T>(this->pos_);}
};
template<class T> class my_list_const_iterator: public my_bidirectional_iterator<node<T>,const T*,const T&>
{
friend class my_list_iterator<T>;
using my_bidirectional_iterator<node<T>,const T*,const T&>::my_bidirectional_iterator;
};
template<class T> class my_list {
public:
typedef my_list_iterator<T> iterator;
typedef my_list_const_iterator<T> const_iterator;
typedef T value_type;
typedef std::size_t size_type;
// constructors
my_list() {create();}
explicit my_list(size_type n,const T& t=T()) {create(n,t);}
// copy constructor
my_list(const my_list& rhs) {create(rhs.begin(),rhs.end());}
// assignment operator
my_list& operator=(const my_list&);
// destructor
~my_list() {clear();}
// element access
T& front() {return head_->data;}
const T& front() const {return head_->data;}
T& back() {return tail_->prev->data;}
const T& back() const {return tail_->prev->data;}
// iterators
iterator begin() {return iterator(head_);}
const_iterator begin() const {return const_iterator(head_);}
iterator end() {return iterator(tail_);}
const_iterator end() const {return const_iterator(tail_);}
// capacity
bool size() const {return size_;}
bool empty() const {return size_==0;}
// modifiers
void clear();
iterator insert(iterator,const T&);
iterator erase(iterator);
void push_back(const T& t) {insert(end(),t);}
void push_front(const T& t) {insert(begin(),t);}
void pop_back() {erase(iterator(tail_->prev));}
void pop_front() {erase(iterator(head_));}
void resize(size_type);
private:
node<T> *head_,*tail_;
size_type size_;
void create();
void create(size_type, const T& t = T());
void create(const_iterator,const_iterator);
void insertInternal(node<T>*,const T&);
friend class my_list_iterator<T>;
friend class my_list_const_iterator<T>;
};
#include "my_list.hpp"
#endif
And the implementation:
template<class Value,class Pointer,class Reference>
my_bidirectional_iterator<Value,Pointer,Reference> my_bidirectional_iterator<Value,Pointer,Reference>::operator++()
{
pos_ = pos_->next;
return *this;
}
template<class Value,class Pointer,class Reference>
my_bidirectional_iterator<Value,Pointer,Reference>
my_bidirectional_iterator<Value,Pointer,Reference>::operator++(int)
{
Value* prev= pos_;
pos_ = pos_->next;
return my_bidirectional_iterator(prev);
}
template<class Value,class Pointer,class Reference>
my_bidirectional_iterator<Value,Pointer,Reference>
my_bidirectional_iterator<Value,Pointer,Reference>::operator--()
{
pos_ = pos_->prev;
return *this;
}
template<class Value,class Pointer,class Reference>
my_bidirectional_iterator<Value,Pointer,Reference>
my_bidirectional_iterator<Value,Pointer,Reference>::operator--(int)
{
Value* next= pos_;
pos_ = pos_->prev;
return my_bidirectional_iterator(next);
}
template<class T> my_list<T>& my_list<T>::operator=(const my_list& rhs)
{
if (this!=&rhs)
{
clear();
create(rhs.begin(),rhs.end());
}
return *this;
}
template<class T> void my_list<T>::clear()
{
if (size_!=0)
{
node<T>* first = head_;
while (first!=0)
{
node<T>* next = first->next;
delete first;
first = next;
}
}
head_=tail_=0;
size_=0;
}
template<class T> typename my_list<T>::iterator
my_list<T>::insert(iterator pos,const T& t)
{
// create new node holding the value t
node<T>* new_node = new node<T>(t);
// pointer to node before which t is inserted
// if node doesn't exist make it on the fly
node<T>* p = (pos.pos_)?(pos.pos_):(new node<T>());
// make sure new_node is before p
new_node->next = p;
if (p->prev){
new_node->prev = p->prev;
new_node->prev->next = new_node;
} else
head_ = new_node;
p->prev = new_node;
// make sure that tail_ is set properly if we started with empty list
if (size_==0)
tail_ = p;
// increase size
++size_;
// return iterator pointng to new node
return iterator(new_node);
}
template<class T> typename my_list<T>::iterator my_list<T>::erase(iterator pos)
{
node<T> *p = pos.pos_;
if (p->next!=0)
p->next->prev = p->prev;
else
tail_ = p->prev;
if (p->prev!=0)
p->prev->next = p->next;
else
head_ = p->next;
node<T>* n = p->next;
delete p;
--size_;
return iterator(n);
}
template<class T> void my_list<T>::resize(size_type n)
{
while (n<size_)
pop_back();
while (n>size)
push_back(T());
}
template<class T> void my_list<T>::create()
{
head_=tail_=0;
size_ = 0;
}
template<class T> void my_list<T>::create(size_type n,const T& t)
{
create();
for (size_type i=0;i!=n;++i)
push_back(t);
}
template<class T> void my_list<T>::create(const_iterator b,const_iterator e)
{
create();
while (b!=e)
push_back(*b++);
}
I am using GCC 8.3.0 which, if I checked correctly, implements C++14 by default. In any case, I tried compiling with -std=c++11
and it compiles fine.
bool size()
.) And always test for edge cases (zero, min/max value, even/odd numbers, etc.) \$\endgroup\$