If that was confusing I provide an example here.
http://codereview.stackexchange.com/a/28393/507 https://codereview.stackexchange.com/a/28393/507
If that was confusing I provide an example here.
http://codereview.stackexchange.com/a/28393/507
If that was confusing I provide an example here.
https://codereview.stackexchange.com/a/28393/507
###Description:
The sentinel
is always there. You also need to make the list circular. That way the first element is next
from the sentinel
and the last is element prev
from the sentinel
.
When you construct the end()
iterator use the sentinel
as it is one past the end.
When you construct the begin
iterator use the sentinel->next
. Note if the list is empty the the circula nature makes it the same as the end()
iterator.
template<typename T>
class LList
{
// Define the `Element` internally
struct Element { /* STUFF */ };
// Just need one member (the end)
// Because we are using a sentenal first and last are
// one element away. So we can get to both ends by following
// a single pointer.
Element* sentenal;
std::size_t size; // keep track of size locally
public:
// constructors/destructors.
// Because we are a doing memory management we need to
// implement the rule of 3. Since we can do move semantics
// and it makes sense to extend this to the rule of 5.
LList(); // default constructor
LList(LList const& copy); // copy constructor
LList(LList&& move); // move constructor
~LList(); // destructor
LList& operator=(LList copy); // copy assignment (use copy and swap)
LList& operator=(LList&& move); // move assignment (use swap)
void swap(LList& rhs) noexcept; // swap (is no exception) but
// useful in so many ways.
void push_back(T const& value); // push back copy by value
void push_back(T&& value); // push back move value into container.
/* VERY Advanced but useful the new emplace back */
template<ARGS...>
void emplace_back(ARGS args...); // Use T's constructor
// Always want to test if a container is empty.
// Some methods are only valid if empty is not true.
bool empty() const;
bool size() const;
// Undefined behavior if empty()
// Access front or back members.
// But provide normal and const versions.
T& front();
T const& front() const;
T& back();
T const& back() const;
// Define an internal iterator.
// Use the `std::iterator` as a base for your iterator.
struct Iterator: public std::iterator<std::bidirectional_iterator_tag, T>
{ /* STUFF */ };
// Need to define types that
// are used by standard algorithms.
typedef STUFF_WITH Iterator iterator;
typedef STUFF_WITH Iterator const_iterator;
// The iterator interface.
iterator begin();
const_iterator begin() const;
const_iterator cbegin() const;
iterator end();
const_iterator end() const;
const_iterator cend() const;
// Deleting elements.
void pop_front();
void pop_back();
void del(iterator iter); // Note delete via iterator
};
We can simplify the iterator:
struct Iterator: public std::iterator<std::bidirectional_iterator_tag, T>
{
Element* current;
Iterator(Element* start)
: current(start)
{}
// Increment/Decrement pre and post fix
Iterator& operator++() {current = current->next;return *this;}
Iterator& operator--() {current = current->prev;return *this;}
Iterator operator++(int) {Iterator r(*this);this->operator++();return r;} // Note define in terms of prefix.
Iterator operator--(int) {Iterator r(*this);this->operator--();return r;} // Note define in terms of prefix.
// Test against other iterators.
bool operator==(Iterator const& rhs) const {return current == rhs.current;}
bool operator!=(Iterator const& rhs) const {return current != rhs.current;}
};
// Have specific versions for normal iterator.
// And another for a const version of the iterator.
struct IteratorNorm: public Iterator
{
IteratorNorm(Element* st): Iterator(st) {}
T& operator*() {return static_cast<DataElement*>(this->current)->data;}
};
struct IteratorConst: public Iterator
{
IteratorConst(Element* st): Iterator(st) {}
T const& operator*() const {return static_cast<DataElement*>(this->current)->data;}
};
// Create the appropriate types need by algorithm
typedef IteratorNorm iterator;
typedef IteratorConst const_iterator;
template<typename T>
class LList
{
// Define the `Element` internally
struct Element { /* STUFF */ };
// Just need one member (the end)
// Because we are using a sentenal first and last are
// one element away. So we can get to both ends by following
// a single pointer.
Element* sentenal;
std::size_t size; // keep track of size locally
public:
// constructors/destructors.
// Because we are a doing memory management we need to
// implement the rule of 3. Since we can do move semantics
// and it makes sense to extend this to the rule of 5.
LList(); // default constructor
LList(LList const& copy); // copy constructor
LList(LList&& move); // move constructor
~LList(); // destructor
LList& operator=(LList copy); // copy assignment (use copy and swap)
LList& operator=(LList&& move); // move assignment (use swap)
void swap(LList& rhs) noexcept; // swap (is no exception) but
// useful in so many ways.
void push_back(T const& value); // push back copy by value
void push_back(T&& value); // push back move value into container.
/* VERY Advanced but useful the new emplace back */
template<ARGS...>
void emplace_back(ARGS args...); // Use T's constructor
// Always want to test if a container is empty.
// Some methods are only valid if empty is not true.
bool empty() const;
bool size() const;
// Undefined behavior if empty()
// Access front or back members.
// But provide normal and const versions.
T& front();
T const& front() const;
T& back();
T const& back() const;
// Define an internal iterator.
// Use the `std::iterator` as a base for your iterator.
struct Iterator: public std::iterator<std::bidirectional_iterator_tag, T>
{ /* STUFF */ };
// Need to define types that
// are used by standard algorithms.
typedef STUFF_WITH Iterator iterator;
typedef STUFF_WITH Iterator const_iterator;
// The iterator interface.
iterator begin();
const_iterator begin() const;
const_iterator cbegin() const;
iterator end();
const_iterator end() const;
const_iterator cend() const;
// Deleting elements.
void pop_front();
void pop_back();
void del(iterator iter); // Note delete via iterator
};
###Description:
The sentinel
is always there. You also need to make the list circular. That way the first element is next
from the sentinel
and the last is element prev
from the sentinel
.
When you construct the end()
iterator use the sentinel
as it is one past the end.
When you construct the begin
iterator use the sentinel->next
. Note if the list is empty the the circula nature makes it the same as the end()
iterator.
template<typename T>
class LList
{
// Define the `Element` internally
struct Element { /* STUFF */ };
// Just need one member (the end)
// Because we are using a sentenal first and last are
// one element away. So we can get to both ends by following
// a single pointer.
Element* sentenal;
std::size_t size; // keep track of size locally
public:
// constructors/destructors.
// Because we are a doing memory management we need to
// implement the rule of 3. Since we can do move semantics
// and it makes sense to extend this to the rule of 5.
LList(); // default constructor
LList(LList const& copy); // copy constructor
LList(LList&& move); // move constructor
~LList(); // destructor
LList& operator=(LList copy); // copy assignment (use copy and swap)
LList& operator=(LList&& move); // move assignment (use swap)
void swap(LList& rhs) noexcept; // swap (is no exception) but
// useful in so many ways.
void push_back(T const& value); // push back copy by value
void push_back(T&& value); // push back move value into container.
/* VERY Advanced but useful the new emplace back */
template<ARGS...>
void emplace_back(ARGS args...); // Use T's constructor
// Always want to test if a container is empty.
// Some methods are only valid if empty is not true.
bool empty() const;
bool size() const;
// Undefined behavior if empty()
// Access front or back members.
// But provide normal and const versions.
T& front();
T const& front() const;
T& back();
T const& back() const;
// Define an internal iterator.
// Use the `std::iterator` as a base for your iterator.
struct Iterator: public std::iterator<std::bidirectional_iterator_tag, T>
{ /* STUFF */ };
// Need to define types that
// are used by standard algorithms.
typedef STUFF_WITH Iterator iterator;
typedef STUFF_WITH Iterator const_iterator;
// The iterator interface.
iterator begin();
const_iterator begin() const;
const_iterator cbegin() const;
iterator end();
const_iterator end() const;
const_iterator cend() const;
// Deleting elements.
void pop_front();
void pop_back();
void del(iterator iter); // Note delete via iterator
};
We can simplify the iterator:
struct Iterator: public std::iterator<std::bidirectional_iterator_tag, T>
{
Element* current;
Iterator(Element* start)
: current(start)
{}
// Increment/Decrement pre and post fix
Iterator& operator++() {current = current->next;return *this;}
Iterator& operator--() {current = current->prev;return *this;}
Iterator operator++(int) {Iterator r(*this);this->operator++();return r;} // Note define in terms of prefix.
Iterator operator--(int) {Iterator r(*this);this->operator--();return r;} // Note define in terms of prefix.
// Test against other iterators.
bool operator==(Iterator const& rhs) const {return current == rhs.current;}
bool operator!=(Iterator const& rhs) const {return current != rhs.current;}
};
// Have specific versions for normal iterator.
// And another for a const version of the iterator.
struct IteratorNorm: public Iterator
{
IteratorNorm(Element* st): Iterator(st) {}
T& operator*() {return static_cast<DataElement*>(this->current)->data;}
};
struct IteratorConst: public Iterator
{
IteratorConst(Element* st): Iterator(st) {}
T const& operator*() const {return static_cast<DataElement*>(this->current)->data;}
};
// Create the appropriate types need by algorithm
typedef IteratorNorm iterator;
typedef IteratorConst const_iterator;
#If I was going to do it here is where I would start:
###Empty List
#############
/-># sentenal #<-
--#Prev Next#--/
#############
###One Element List
#############<-------------------------
# DATA # ############# |
/--#Prev Next#----------># sentenal # |
| #############<----------#Prev Next#--/
------------------------->#############
#If I was going to do it here is where I would start:
#If I was going to do it here is where I would start:
###Empty List
#############
/-># sentenal #<-
--#Prev Next#--/
#############
###One Element List
#############<-------------------------
# DATA # ############# |
/--#Prev Next#----------># sentenal # |
| #############<----------#Prev Next#--/
------------------------->#############