I have an iterator class for a queue (implemented as a circular array). I attach the code below. The problem is with the ++ operator. Once it reaches the end of the array, it circles back to its beginning, so the iterator points to the 1st element. It works fine but I have no way of implementing then end() iterator using this method. The functions that return the begin() and end() iterators inside the queue class can be seen at the bottom. The end() iterator should point to the rear of the queue, but when the array is full and rear equals the size of the array is does not work, the ++ operator will circle back instead of allowing it to the return the true end(), which is the element pointed by the rear. Any suggestions regarding this issue?
class IteratorForwQueue : std::iterator<std::forward_iterator_tag, P*> {
public:
typedef IteratorForwQueue iter;
IteratorForwQueue(P* e, Queue* q) : elem(e), _queue(q) {}
IteratorForwQueue(const IteratorForwQueue& it, Queue* q) :
elem(it.elem), _queue(q) {}
iter& operator++() {
if(elem >= (_queue->_elems + (_queue->_size - 1)) &&
_queue->_rear != _queue->_size)
elem = &(_queue->_elems[0]); // circle back passed the array
else
++elem;
return *this;
}
P& operator*() { return *elem;}
P* operator->() { return elem; }
bool operator==(const iter& it) { return elem == it.elem; }
bool operator==(const P& e) { return e == *elem; }
bool operator!=(const iter& it) { return elem != it.elem; }
bool operator!=(const P& e) { return e != *elem; }
private:
P* elem;
Queue<P>* _queue;
}; // end of iterator class
// ....
IteratorForwQueue begin() { return IteratorForwQueue(_elems + _front, this); }
IteratorForwQueue end() { return IteratorForwQueue(_elems + _rear, this); }
How about this, based on the idea that end does not have to be a "past-the-end" iterator, it can be anything:
class IteratorForwQueue : std::iterator<std::forward_iterator_tag, P*> {
public:
typedef IteratorForwQueue iter;
IteratorForwQueue(P* e, size_t sz, size_t rear) : elem(e), _base(e), _sz(sz), _rear(rear) {}
IteratorForwQueue(const IteratorForwQueue& it, size_t sz) :
elem(it.elem), _base(it.elem), _sz(sz) {}
iter& operator++() {
if(elem - _base == _rear) {
elem = nullptr;
_base = 0;
_sz = 0;
_rear = 0;
return *this;
}
if(elem >= _base + (_sz - 1))
elem = _base; // circle back passed the array
else
++elem;
return *this;
}
P& operator*() { return *elem;}
P* operator->() { return elem; }
bool operator==(const iter& it) { return elem == it.elem; }
bool operator==(const P& e) { return e == *elem; }
bool operator!=(const iter& it) { return elem != it.elem; }
bool operator!=(const P& e) { return e != *elem; }
private:
P* elem;
P* _base; // can't access members of outer class
size_t _sz; // can't access members of outer class
size_t _rear;
}; // end of iterator class
// ....
IteratorForwQueue begin() { return IteratorForwQueue(_elems + _front, _size, _rear); }
IteratorForwQueue end() { return IteratorForwQueue(nullptr, 0, 0); }
const IteratorForwQueue cbegin() const { return IteratorForwQueue(_elems + _front, _size); }
const IteratorForwQueue cend() const { return IteratorForwQueue(_elems + _rear, _size); }
BTW, it would be better to have a pointer back to the original datastructure, make the iterator a friend class and fetch _base, _sz and _rear on demand.
Related
The other day I wanted to try writing my own iterators for a vector, of course, the most primitive example, since there is a lot of confusing code in the c++ standards. So the usual iterator for a vector in the forward direction works fine, but there was a problem with the reverse iterator. I have it completely built on the base iterator, only I changed / inverted the operators specifically for the reverse iterator.
template<typename Vector>
class VectorRevIterator : public VectorIterator<Vector> //This is normal (work) vector iterator
{
public:
using Base = VectorIterator<Vector>;
VectorRevIterator(PointerType ptr) noexcept : Base(ptr) {};
VectorRevIterator(const VectorRevIterator& other) : Base(other) { *this = other; };
VectorRevIterator& operator++()
{
Base::operator--(); //--ptr;
return *this;
}
VectorRevIterator operator++(int)
{
VectorRevIterator itr = *this;
Base::operator--(); //--*this;
return itr;
}
VectorRevIterator& operator--()
{
Base::operator++(); //++ptr;
return *this;
}
VectorRevIterator operator--(int)
{
VectorRevIterator itr = *this;
Base::operator++(); //++*this;
return itr;
}
VectorRevIterator& operator+=(const PointerType otherPtr)
{
Base::operator-=(otherPtr); //ptr -= otherPtr;
return *this;
}
VectorRevIterator operator+(const PointerType otherPtr)
{
VectorRevIterator itr = *this;
Base::operator-(otherPtr); // itr -= otherPtr
return *this;
}
VectorRevIterator& operator-=(const PointerType otherPtr) { return Base::operator+=(otherPtr); }
VectorRevIterator operator-(const PointerType otherPtr) { Base::operator+(otherPtr); }
ReferenceType operator*() const { return *ptr; }
PointerType operator->() const { return std::_Const_cast(Base::operator->()); }
};
Access to iterator from Vector:
template<typename T>
class Vector
{
public:
using ValueType = T;
using PointerType = ValueType*;
using ReferenceType = ValueType&;
using ReverseIterator = VectorRevIterator<Vector<T>>;
public:
T* data;
size_t size;
size_t capacity;
...
// construct/destructor
// custom allocator
// index operators
...
ReverseIterator rBegin() { return ReverseIterator(data + size); }
ReverseIterator rEnd() { return ReverseIterator(data); }
};
The problem itself is that when I try to go through all the elements in the opposite direction VectorRevIterator. When trying to output all this to the console, it seems to shift one element forward and does not see/cannot read the characters of the first element. But then output all the elements, only without the last one.
Here a example:
Vector<String> values;
values.emplaceBack("1");
values.emplaceBack("2");
values.emplaceBack("3");
values.emplaceBack("4");
values.emplaceBack("5");
Vector<String>::ReverseIterator revIt = values.rBegin();
// output with spdlog
for (revIt; revIt != values.rEnd(); ++revIt)
INFO(*revIt); // error on first iteration, but print only 1, 2, 3, 4
// ouput with std::cout
for (revIt; revIt != values.rEnd(); ++revIt)
std::cout << *revIt << std::endl; // doesn't print anything
How solve this problem? To make reverse iterator it just need to revert operators ++ -- += -= and rBegin rEnd functions. Or maybe im forgotten about something?
rBegin() returns an iterator to the first element:
ReverseIterator rBegin() { return ReverseIterator(data + size); }
But it's pointing one element beyond the end and can't be dereferenced. You need to dereference the element before it.
You could therefore adjust the dereference operator in the VectorRevIterator version:
ReferenceType operator*() const { return *std::prev(ptr); }
i have to implement a forward iterator on a stack based on arrays. I can't use std::vectors or anything, i just need that. My development of this program stopped when i begun with forward iterator, and in particular with the operators.
I have a method that takes a generic sequence, and from that, given an offset, creates a stack:
template <typename IterT>
stack(IterT begin, IterT end) : _stack(0), _size(0), _capacity(0) {
try {
for(; begin!=end; ++begin) {
push(static_cast<T>(*begin));
}
}
catch(...) {
clear(); //my method to destroy the stack
throw;
}
}
In my main i do the following:
int a[5] = {1, 2, 3, 4, 5};
stack<int> sint(a, a+5);
cout << sint << endl;
But when the code runs the stack is created but not printed. Can somebody help me? And also give me other helps(on code indentation, improvements, etc...) Thank you, I will post the iterator code forward.
class const_iterator {
const T* data;
unsigned int index;
public:
typedef std::forward_iterator_tag iterator_category;
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef const T* pointer;
typedef const T& reference;
const_iterator() : data(0){}
const_iterator(const T* arr) : data(arr) {}
const_iterator(const const_iterator &other)
: data(other.data){ }
const_iterator& operator=(const const_iterator &other) {
data = other.data;
return *this;
}
~const_iterator() {
data = 0;
}
reference operator*() const {
return *data;
}
pointer operator->() const {
return &(data);
}
const_iterator operator++(int) {
const_iterator tmp(*this);
++*this;
return tmp;
}
const_iterator& operator++() {
++data;
return *this;
}
bool operator==(const const_iterator &other) const {
return data[index] == other.data[index];
}
bool operator!=(const const_iterator &other) const {
return data[index] != other.data[index] ;
}
private:
friend class stack;
const_iterator(unsigned int ind) :
index(ind){}
}; // class const_iterator
const_iterator begin() const {
cout << "begin" << _stack[_size-1] << endl;
return const_iterator(_stack[_size-1]);
}
const_iterator end() const {
cout << "end" << _stack[0] << endl;
return const_iterator(_stack[0]);
}
Last but not least i redefined the << operator to fit the iterator:
template <typename T>
std::ostream &operator<<(std::ostream &os, const stack<T> &st) {
typename stack<T>::const_iterator i, ie;
for(i = st.begin(), ie = st.end(); i!=ie; ++i){
os << *i << std::endl;
}
return os;
}
The code for the stack is the following (I omitted something for readability).
stack()
: _capacity(0), _size(0), _stack(0){}
void push (const T &value){
if (_size == _capacity){ //raddoppio la dimensione
if(_capacity == 0)
++_capacity;
_capacity *= 2;
T* tmp = new T[_capacity];
copy_n(_stack, _size, tmp);
swap(_stack, tmp);
delete[] tmp;
}
_stack[_size] = value;
++_size;
}
void pop(){
T _tmp;
if(!is_empty()){
_tmp = _stack[_size-1];
--_size;
}
}
If you want to create an iterator that looks like a pointer, you don't need index, because data plays its role. Comparison operator should compare datas, not values:
bool operator==(const const_iterator &other) const {
return data == other.data;
}
If you want to create a reverse iterator, it is slightly more complex. First, operator++ should decrement data. Second, dereference operator should return not *data, but *(data - 1). Third, data in the begin() iterator should point to stack[size], and data in the end() iterator should point to stack[0]. You don't need a destructor in any case.
I followed the previous advices and here's the edited result, still i can't figure out how to properly use the constructor in the private section
class const_iterator {
const T *data;
public:
/* ITERATOR TRAITS HERE */
const_iterator() : data(0){}
const_iterator(const T* arr) : data(arr) {}
const_iterator(const const_iterator &other)
: data(other.data){ }
const_iterator& operator=(const const_iterator &other) {
data = other.data;
return *this;
}
~const_iterator() {
data = 0;
}
reference operator*() const {
return *data;
}
pointer operator->() const {
return &(data);
}
const_iterator operator++(int) {
const_iterator tmp(*this);
++*this;
return tmp;
}
const_iterator& operator++() {
++data;
return *this;
}
bool operator==(const const_iterator &other) const {
return data == other.data;
}
bool operator!=(const const_iterator &other) const {
return data != other.data;
}
private:
friend class stack;
const_iterator(const T *d) {
data = d;
}
}; // classe const_iterator
const_iterator begin() const {
return const_iterator(_stack[_size-1]);
}
const_iterator end() const {
return const_iterator(_stack[0]);
}
I wrote this Vector class and one nested Iterator class and one const_Iterator class, now I have to secure my Vector so my Iterator doesn't point beyond .end() with for instance my operator ++ method and to throw an exception if i try.
so in my iterator class I cannot access .end() because its a vector method. so I was thinking, instead of pointing on values in my Iterator class I point on the whole vector but i cannot access .end() with a Vector* .
Am I on the right way to the solution and if I am how can I pass my Vector so I can use it in the way I intend to?
class Vector{
public:
using value_type= double;
using size_type= size_t;
using difference_type= ptrdiff_t;
using reference = double&;
using const_reference= const double&;
using pointer = double*;
using const_pointer= const double*;
using iterator = double*;
using const_iterator= const double*;
private:
size_t sz;
size_t max_sz;
double* values=nullptr;
class const_Iterator{
public:
using value_type = double;
using difference_type = ptrdiff_t;
using reference = double&;
using pointer = double*;
using iterator_category = std::forward_iterator_tag;
private:
double* ptr;
size_t cnt;
public:
const_Iterator(double* p){
ptr=p;
cnt=0;
}
const_Iterator& operator++ () {
ptr++;
cnt = 0;
return *this;
}
bool operator==(const const_Iterator& rop)const {
return this->ptr == rop.ptr;
}
bool operator!=(const const_Iterator& rop)const {
return this->ptr != rop.ptr;
}
const double operator* () const {
return *ptr;
}
friend Vector::difference_type operator-(const Vector::const_Iterator& lop,const Vector::const_Iterator& rop) {
return lop.ptr-rop.ptr;
}
};
class Iterator{
public:
using value_type = double;
using difference_type = ptrdiff_t;
using reference = double&;
using pointer = double*;
using iterator_category = std::forward_iterator_tag;
private:
double* ptr;
size_t cnt;
public:
Iterator(double* p){
ptr=p;
cnt=0;
}
Iterator& operator++() {
ptr++;
cnt = 0;
return *this;
}
Iterator operator++(int){
Iterator a(ptr);
ptr++;
return a;
}
bool operator==( Iterator& rop) {
return this->ptr != rop.ptr;
}
bool operator!=(const Iterator& rop) {
return this->ptr != rop.ptr;
}
double& operator*() {
return *ptr;
}
operator const_Iterator() const{
return const_Iterator(ptr);
};
};
const_Iterator end() const{return const_Iterator(values+sz);}
const_Iterator begin() const{return const_Iterator(values);}
Iterator begin() { return values; }
Iterator end() { return values + sz; }
size_t min_sz = 5;
Vector();
Vector(size_t);
Vector(const Vector&);
Vector (initializer_list<double> );
void push_back(double);
void reserve(size_t);
void pop_back();
bool empty();
void clear();
Vector& operator=(const Vector&);
const double& operator[] (size_t) const;
double& operator[] (size_t) ;
void fit_to_shrink();
size_t size()const {return sz;}
ostream& print(ostream&) const;
};
Your iterator may look like:
class Iterator{
public:
// ... using type
private:
Vector* parent;
std::size_t index;
public:
Iterator(Vector& v, std::size_t index) : ptr(&v), index(index) {}
Iterator& operator++() {
if (index == parent->size()) {
throw std::runtime_error("++ on end iterator");
}
++index;
return *this;
}
Iterator operator++(int){
Iterator old(*this);
++(*this);
return old;
}
bool operator==(const Iterator& rhs) const {
if (parent != rhs.parent) {
throw std::runtime_error("You compare iterator of different containers");
}
return index == rhs.index;
}
bool operator!=(const Iterator& rop) const { return !(*this == rhs); }
double& operator*() { return parent->at(index); } // `at` throws on invalid index
// ...
};
And your vector:
Iterator Vector::begin() { return Iterator(this, 0);}
Iterator Vector::end() { return Iterator(this, size());}
I have the following code written in what would now be called C++98
It implements a doubly linked list, and I was considering updating it to C++11, but I had the following concerns:
it would seem that using std::unique_ptr makes sense, but there
still needs to be other pointers "sharing" the unique_ptr. I.e if
nxt is a unique_ptr, what do I do with prv and iterator::it?
it would seem that it should take an allocator as a template
paraemter, but it will be an allocator of type T, not LLNode?
are there any traits, that need to be "registered"?
if move semantics are used, what methods should be defined e.g.
operator=() && etc.?
what undefined behavior (if any) needs to be corrected?
what am i not concerned about that I should be?
An accepted answer would briefly discuss the concerns, and then reimplement in C++11 (admittedly, it's a lot of code, but most will be just cut and paste)
A good answer will briefly discuss the concerns, and then reimplement in C++11 the minimal amount to illustrate how to apply those concepts
using standard algorythms would make sense, using a standard container defeats the purpose
appropriate use of C++11 features is encouraged where it makes sense e.g. foreach as opposed to just using lambdas because it will work
This is not homework
The code:
template <class T>
class LinkedList
{
class LLNode
{
public:
LLNode() // For Sentinel (requires that T has a default constructor)
{} // set prv/nxt directly
explicit LLNode(const T& x) : data(x) // For normal nodes
{} // set prv/nxt directly
T& get_data() const
{
return const_cast<T&>(data);
}
LLNode * prv;
LLNode * nxt;
private:
T data;
};
public:
class iterator
{
public:
iterator(const LinkedList * p, LLNode * i)
: parent(const_cast<LinkedList *>(p)), it(i)
{}
iterator& operator ++() // pre
{
it = it->nxt;
return *this;
}
iterator operator ++(int) // post
{
iterator ret=*this;
it = it->nxt;
return ret;
}
iterator& operator --() // pre
{
it = it->prv;
return *this;
}
iterator operator --(int) //post
{
iterator ret=*this;
it = it->prv;
return ret;
}
T& operator *() const
{
return it->get_data();
}
T * operator ->() const
{
return &(it->get_data());
}
bool operator ==(const iterator& rhs) const
{
return it == rhs.it;
}
bool operator !=(const iterator& rhs) const
{
return it != rhs.it;
}
void erase()
{
parent->remove(it);
}
void insert_after(const T& x)
{
LLNode * add= new LLNode(x);
parent->insert(it, add);
}
void insert_before(const T& x)
{
LLNode * add= new LLNode(x);
parent->insert(it->prv, add);
}
private:
LinkedList * parent;
LLNode * it;
};
// Linked List class definition
LinkedList()
{
init();
}
LinkedList(const LinkedList& rhs)
{
init();
cp(rhs);
}
~LinkedList()
{
rm();
}
LinkedList operator =(const LinkedList& rhs)
{
if (this != &rhs)
{
rm();
cp(rhs);
}
return *this;
}
iterator begin() const
{
return iterator(this, sentinel.nxt);
}
iterator rbegin() const
{
return iterator(this, sentinel.prv);
}
iterator end() const
{
return iterator(this, const_cast<LLNode *>(&sentinel));
}
T& get_first() const // illegal if is_empty() is true
{
return sentinel.nxt->get_data();
}
T& get_last() const // illegal if is_empty() is true
{
return sentinel.prv->get_data();
}
size_t size() const
{
return count;
}
bool is_empty() const
{
return count==0;
}
void insert_first(const T& x)
{
LLNode * add= new LLNode(x);
insert(&sentinel, add);
}
void insert_last(const T& x)
{
LLNode * add= new LLNode(x);
insert(sentinel.prv, add);
}
void erase_first() // illegal if is_empty() is true
{
remove(sentinel.nxt);
}
void erase_last() // illegal if is_empty() is true
{
remove(sentinel.prv);
}
private:
void insert(LLNode * before, LLNode * added)
{
LLNode * after=before->nxt;
added->prv=before;
added->nxt=after;
before->nxt=added;
after->prv=added;
++count;
}
void remove(LLNode * node) // illegal if is_empty() is true
{
node->prv->nxt=node->nxt;
node->nxt->prv=node->prv;
delete node;
--count;
}
void cp(const LinkedList& rhs)
{
for (iterator i=rhs.begin(); i != rhs.end(); ++i)
{
insert_last(*i);
}
}
void rm()
{
LLNode * run=sentinel.nxt;
while (run != &sentinel)
{
LLNode * dead=run;
run=run->nxt;
delete dead;
}
}
void init()
{
count=0;
sentinel.nxt = sentinel.prv = &sentinel; // setup circular ref
}
LLNode sentinel;
size_t count;
};
EDIT - C++11 attempt based on Mooing Duck's answer :
template <class T, class ALLOC=std::allocator<T> >
class LinkedList
{
struct LLNode
{
LLNode * prv;
LLNode * nxt;
T& get_data() { return data; }
T data;
};
public:
class iterator
{
public:
using difference_type = ptrdiff_t;
using value_type = T;
using reference = T&;
using pointer = T*;
using iterator_category = std::bidirectional_iterator_tag;
iterator(LinkedList * p, LLNode * i) : parent(p), it(i)
{}
iterator& operator ++() // pre
{
it = it->nxt;
return *this;
}
iterator operator ++(int) // post
{
iterator ret=*this;
it = it->nxt;
return ret;
}
iterator& operator --() // pre
{
it = it->prv;
return *this;
}
iterator operator --(int) //post
{
iterator ret=*this;
it = it->prv;
return ret;
}
const T& operator *() const
{
return it->get_data();
}
T& operator *()
{
return it->get_data();
}
const T * operator ->() const
{
return &(it->get_data());
}
T * operator ->()
{
return &(it->get_data());
}
bool operator ==(const iterator& rhs) const
{
return it == rhs.it;
}
bool operator !=(const iterator& rhs) const
{
return it != rhs.it;
}
void erase()
{
parent->remove(it);
}
void insert_after(T& x)
{
auto add=parent->alloc_node(x);
parent->insert(it->nxt, add);
}
void insert_before(T& x)
{
auto add=parent->alloc_node(x);
parent->insert(it, add);
}
private:
LinkedList * parent;
LLNode * it;
};
class const_iterator
{
public:
using difference_type = ptrdiff_t;
using value_type = const T;
using reference = const T&;
using pointer = const T*;
using iterator_category = std::bidirectional_iterator_tag;
const_iterator(const LinkedList * p, const LLNode * i) : parent(p),
it(const_cast<LLNode *>(i))
{}
const_iterator(iterator& cvt) : parent(cvt.parent), it(cvt.it)
{}
const_iterator& operator ++() // pre
{
it = it->nxt;
return *this;
}
const_iterator operator ++(int) // post
{
const_iterator ret=*this;
it = it->nxt;
return ret;
}
const_iterator& operator --() // pre
{
it = it->prv;
return *this;
}
const_iterator operator --(int) //post
{
const_iterator ret=*this;
it = it->prv;
return ret;
}
const T& operator *() const
{
return it->get_data();
}
const T * operator ->() const
{
return &(it->get_data());
}
bool operator ==(const const_iterator& rhs) const
{
return it == rhs.it;
}
bool operator !=(const const_iterator& rhs) const
{
return it != rhs.it;
}
private:
const LinkedList * parent;
LLNode * it;
};
using my_alloc=typename
std::allocator_traits<ALLOC>::template rebind_alloc<LLNode>;
using my_traits=typename
std::allocator_traits<ALLOC>::template rebind_traits<LLNode>;
// Linked List class definition
LinkedList(const ALLOC& alloc = ALLOC() ) : mem(alloc)
{
init();
}
LinkedList(const LinkedList& rhs) : mem(rhs.mem)
{
init();
cp(rhs);
}
LinkedList(LinkedList&& rhs) : mem(rhs.mem) // Move
{
init();
shallow_cp(rhs);
}
~LinkedList()
{
rm();
}
LinkedList operator =(const LinkedList& rhs)
{
if (this != &rhs)
{
rm();
cp(rhs);
}
return *this;
}
LinkedList operator =(LinkedList&& rhs) // Move
{
if (this != &rhs)
{
rm();
shallow_cp(rhs);
}
return *this;
}
const_iterator begin() const
{
return const_iterator(this, sentinel.nxt);
}
iterator begin()
{
return iterator(this, sentinel.nxt);
}
const_iterator rbegin() const
{
return const_iterator(this, sentinel.prv);
}
iterator rbegin()
{
return iterator(this, sentinel.prv);
}
const_iterator end() const
{
return const_iterator(this, &sentinel);
}
iterator end()
{
return iterator(this, &sentinel);
}
T& front() // illegal if is_empty() is true
{
return sentinel.nxt->get_data();
}
T& back() // illegal if is_empty() is true
{
return sentinel.prv->get_data();
}
size_t size() const
{
return count;
}
bool is_empty() const
{
return count==0;
}
void insert_first(const T& x)
{
LLNode * add=alloc_node(x);
insert(&sentinel->nxt, add);
}
void insert_last(const T& x)
{
LLNode * add=alloc_node(x);
insert(&sentinel, add);
}
void erase_first() // illegal if is_empty() is true
{
remove(sentinel.nxt);
}
void erase_last() // illegal if is_empty() is true
{
remove(sentinel.prv);
}
private:
LLNode * alloc_node(const T& x)
{
auto ret = my_traits::allocate(mem,1);
my_traits::construct(mem, &(ret->data), x);
return ret;
}
void unalloc_node(LLNode * dead)
{
my_traits::deallocate(mem, dead, 1);
}
void insert(LLNode * after, LLNode * added)
{
LLNode * before=after->prv;
added->prv=before;
added->nxt=after;
before->nxt=added;
after->prv=added;
++count;
}
void remove(LLNode * node) // illegal if is_empty() is true
{
node->prv->nxt=node->nxt;
node->nxt->prv=node->prv;
unalloc_node(node);
--count;
}
void cp(const LinkedList& rhs)
{
mem = rhs.mem;
for (const_iterator i=rhs.begin(); i != rhs.end(); ++i)
{
insert_last(*i);
}
}
void shallow_cp(LinkedList& rhs)
{
if (rhs.count)
{
count=rhs.count;
sentinel=rhs.sentinel; // shallow copy
// fix the links to the old sentinel
sentinel.nxt.prv=&sentinel;
sentinel.prv.nxt=&sentinel;
rhs.init();
}
}
void rm()
{
LLNode * run=sentinel.nxt;
while (run != &sentinel)
{
LLNode * dead=run;
run=run->nxt;
unalloc_node(dead);
}
}
void init()
{
count=0;
sentinel.nxt = sentinel.prv = &sentinel; // setup circular ref
}
LLNode sentinel;
size_t count;
my_alloc mem;
};
Is anything missing/wrong?
it would seem that using std::unique_ptr makes sense, but there still needs to be other pointers "sharing" the unique_ptr. I.e if nxt is a unique_ptr, what do I do with prv and iterator::it?
Don't. (1) It makes various internal algorithms trickier to perform without accidentally deleting a node, and (2) unique_ptr stores the deleter, in your case that's either (A) a copy of the iterator, or (B) a pointer to the iterator. Either one is a waste of space. The container should store the allocator, and the container should handle deletions.
it would seem that it should take an allocator as a template paraemter, but it will be an allocator of type T, not LLNode?
Containers take an allocator of type T, though all of them use a rebound type internally. The standard is that allocators to containers take type T, and that way every std::vector<T, allocator<?>> matches every other. Also, outsiders should not be able to access LLNode. You'd probably store a given_allocator<LLNode> internally though. That's why we have rebinding.
are there any traits, that need to be "registered"?
For containers, no. There are interfaces to match, but those are relatively obvious.
Yes, your iterators are supposed to register traits, but that's easily done by merely adding five typedefs at the front.
typedef ptrdiff_t difference_type; //usually ptrdif_t
typedef T value_type; //usually T
typedef T& reference; //usually T&
typedef T* pointer; //usually T*
typedef std::bidirectional_iterator_tag iterator_category;
if move semantics are used, what methods should be defined e.g. operator=() && etc.?
Obviously the container should be move constructable and move assignable if it makes sense, which it does 99.99% of the time. Even std::array has move operators. Also decide which member functions should support move-only-T (insert range via iterators, but not insert range + count), and which member functions support any T (emplace one).
what undefined behavior (if any) needs to be corrected?
It would appear that your insert(iterator, data) inserts the data after the iterator, when the standard is to insert before the iterator. Your way makes it impossible to add data to the beginning, and makes it possible to add data after the after-the-end.
Your iterators have but do not need remove and insert functions. I wouldn't have mentioned it, but in this case they require the iterators to be twice as big as needed. I'd offer a tentative suggestion to remove them, but only tentative. That's a small penalty, and potentially a useful feature.
Your operator = deallocates everything and then reallocates. It might be handy to simply copy element by element skipping that when possible. Trickier to code though.
You're missing a constructor that constructs from a pair of iterators.
Your iterators allow mutating of data from const containers. THIS ONE IS BIG
get_first and get_last are normally called front and back.
what am i not concerned about that I should be?
The big one that everyone overlooks is exception safety. Your code is exception safe, but that appears to be because your code is amazingly simple, and you skipped all of the "hard" parts :P
For more information review these:
Writing your own STL Container
How to implement an STL-style iterator and avoid common pitfalls?
Edit for your C++11 followup:
missing:
template LLNode(U&& x) //or, one T&& and one const T&
LinkedList(std::initializer_list x)
T& LLNode::get_data() const //important, your code doesn't even compile
T* iterator::operator->() const //important
const_iterator cbegin() const
const_iterator cend() const
const T& front() const //important
const T& back() const //important
template void assign(iter, iter);
void assign(std::initializer_list);
void swap(const X&);
Incorrect:
insert_* should probably take a U&& and use std::forward<U>(x). Variadic templates would be better.
no iterator pair constructor.
memory leak in alloc_node, and you should be constructing a LLNode, you have uninitialized prv/nxt.
unalloc_node never destroys, only deallocates, leading to a memory leak
Missing but pretty unimportant
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
reverse_iterator rbegin(); //optional
const_reverse_iterator rbegin() const;
const_reverse_iterator crbegin() const;
reverse_iterator rend(); //optional
const_reverse_iterator rend() const;
const_reverse_iterator crend() const;
Optional:
not sure why my_alloc and my_traits and they're identical.
inherit privately from the allocator. It's a little wierd, but commonly saves you 8 bytes.
I have two questions about operator overloading.
For an iterator type, how is operator-> overloaded? What value should it return assuming that it is an iterator for a collection of class T objects?
Why does operator++() return by class T& while operator++(int) return by class T? I understand these two represent prefix increment and postfix increment. But why the difference in return value?
EDIT: For Alf. Code is not complete though functioning. Any suggestions for improvement are welcome.
#ifndef DHASH_H
#define DHASH_H
//#include <vector>
#include <memory>
#include <exception>
#include <new>
#include <algorithm>
#include <functional>
namespace MCol
{
template <typename KEY, typename VALUE, typename HASH_FUNCTION, typename KEY_COMP = std::equal_to<KEY> >
class hash_container
{
private:
struct entry
{
KEY _k;
VALUE _v;
entry(const KEY& k, const VALUE& v)
:_k(k), _v(v)
{}
entry& operator=(const entry& e)
{
this->_k = e._k;
this->_v = e._v;
}
};
private:
struct bucket
{
entry* a_Entries;
size_t sz_EntryCount;
bucket()
{
sz_EntryCount = 0;
a_Entries = NULL;
}
~bucket()
{
for(size_t szI = 0; szI < sz_EntryCount; ++szI)
{
a_Entries[szI].~entry();
}
free(a_Entries);
}
//Grow by 1. (Perhaps later try block increment. But wikipedia suggests that there is little difference between the two)
inline bool insert(const KEY& k, const VALUE& v) throw (std::bad_alloc)
{
if(find(k) != NULL)
{
return false;
}
a_Entries = static_cast<entry*>(realloc(a_Entries, sizeof(entry)*(++sz_EntryCount)));
if(a_Entries == NULL)
{
throw std::bad_alloc();
}
new (&a_Entries[sz_EntryCount - 1]) entry(k, v);
return true;
}
//Find entry, swap with last valid entry, remove if necessary.
inline bool erase(const KEY& k) throw(std::bad_alloc)
{
//Forwards or backwards? My guess is backwards is better.
entry* pE = a_Entries;
while(pE != a_Entries + sz_EntryCount)
{
if(pE->_k == k)
{
break;
}
++pE;
}
if(pE == a_Entries + sz_EntryCount)
{
return false;
}
//We don't need to swap if the entry is the only one in the bucket or if it is the last one.
entry* pLast = a_Entries + sz_EntryCount - 1;
if((sz_EntryCount > 1) && (pE != pLast))
{
pE = pLast;
}
a_Entries = static_cast<entry*>(realloc(a_Entries, sizeof(entry)*(--sz_EntryCount)));
if(a_Entries == NULL && sz_EntryCount > 0)
{
throw std::bad_alloc();
}
return true;
}
inline entry* find(const KEY& k) throw()
{
//Better implement a search policy.
entry* pE = a_Entries;
while(pE != a_Entries + sz_EntryCount)
{
if(pE->_k == k)
{
break;
}
++pE;
}
if(pE == a_Entries + sz_EntryCount)
{
return NULL;
}
return pE;
}
};
HASH_FUNCTION& _hf;
KEY_COMP _kc;
size_t sz_TableSize;
double d_MultFactor; //Recalculate this as 1/sz_TableSize everytime sz_TableSize changes.
size_t sz_NextResizeLimit;
size_t sz_EntryCount;
double d_ExpectedLoadFactor;
double d_CurrentLoadFactor;
//If the load factor is relatively high (say >0.5 assuming sizeof(entry) == 2*sizeof(size_t)), it is more space efficient to keep a straight bucket array. But if the load factor is low, memory consumption would be lower if a pointer array of Entries is used here. But, because we would not be much concerned with a little additional memory being used when there are few entries, I think array of bucket objects is better. Further, it bypasses a pointer lookup. May have to reconsider is a situation where multiple hash tables are used (Perhaps as an array).
bucket* a_Buckets;
hash_container(const hash_container&);
hash_container& operator=(const hash_container&);
inline void calculateMultFactor() throw()
{
d_MultFactor = 1.0f/static_cast<double>(sz_TableSize + 1);
//sz_NextResizeLimit = static_cast<size_t>(d_ExpectedLoadFactor*sz_TableSize);
//Have a look at this.
//TODO
}
void resize(size_t szNewSize) throw(std::bad_alloc)
{
if(szNewSize == 0)
{
szNewSize = 1;
}
size_t szOldSize = sz_TableSize;
for(size_t szI = szNewSize; szI < szOldSize; ++szI)
{
a_Buckets[szI].~bucket();
}
a_Buckets = static_cast<bucket*>(realloc(a_Buckets, sizeof(bucket)*szNewSize));
if(a_Buckets == NULL)
{
throw std::bad_alloc();
}
//Unnecessary at the moment. But, just in case that bucket changes.
for(size_t szI = szOldSize; szI < szNewSize; ++szI)
{
new (&a_Buckets[szI]) bucket();
}
sz_TableSize = szNewSize;
calculateMultFactor();
}
inline bucket* get_bucket(const KEY& k) throw()
{
return a_Buckets + _hf(k, sz_TableSize);
}
inline bool need_resizing() const throw()
{
}
public:
//typedef iterator void*;
//typedef const_iterator void*;
//iterator Insert(KEY& k, VALUE& v);
//VALUE& Find(Key& k);
//const VALUE& Find(Key& k);
//iterator Find(KEY k);
//const_iterator Find(KEY k);
//void Delete(KEY& k);
//void Delete(iterator it);
//void Delete(const_iterator it);
class iterator
{
private:
entry* p_Entry;
bucket* p_Bucket;
friend class bucket;
public:
iterator(entry* pEntry)
:p_Entry(pEntry)
{
}
iterator()
{
p_Entry = NULL;
}
iterator(const iterator& it)
{
this->p_Entry = it.p_Entry;
}
inline VALUE& operator*() const
{
return p_Entry->_v;
}
inline bool operator==(const iterator& it) const
{
return this->p_Entry == it.p_Entry;
}
inline bool operator!=(const iterator& it) const
{
return !(*this == it);
}
inline iterator& operator=(const iterator& it)
{
this->p_Entry = it.p_Entry;
}
inline VALUE* operator->() const
{
return &p_Entry->_v;
}
inline iterator operator++()
{
return *this;
}
inline iterator& operator++(int)
{
//WRONG!!!
//TODO : Change this.
return *this;
}
};
private:
iterator _EndIt;
public:
hash_container(HASH_FUNCTION& hf, size_t szTableSize = 1024, double dLoadFactor = 0.7f, KEY_COMP kc = KEY_COMP())throw(std::bad_alloc)
:_hf(hf), sz_TableSize(szTableSize), d_ExpectedLoadFactor(dLoadFactor), _kc(kc)
{
if(d_ExpectedLoadFactor < 0.1f)
{
d_ExpectedLoadFactor = 0.1f;
}
a_Buckets = NULL;
sz_TableSize = 0;
if(szTableSize == 0)
{
szTableSize = 1;
}
resize(szTableSize);
d_CurrentLoadFactor = 0.0f;
sz_EntryCount = 0;
_EndIt = iterator(NULL);
}
virtual ~hash_container()
{
for(size_t szI = 0; szI < sz_TableSize; ++szI)
{
a_Buckets[szI].~bucket();
}
}
inline iterator find(const KEY& k) throw()
{
bucket* pBucket = get_bucket(k);
return pBucket->find(k);
}
inline bool insert(const KEY& k, const VALUE& v) throw(std::bad_alloc)
{
bucket* pBucket = get_bucket(k);
bool bRet = false;
try
{
bRet = pBucket->insert(k, v);
}
catch(std::bad_alloc& e)
{
//What now?
throw e;
}
if(bRet == true)
{
++sz_EntryCount;
}
return bRet;
}
inline VALUE& operator[](const KEY& k) throw(std::bad_alloc)
{
bucket* pBucket = get_bucket(k);
}
inline bool erase(const KEY& k) throw(std::bad_alloc)
{
bucket* pBucket = get_bucket(k);
bool bRet = false;
try
{
bRet = pBucket->erase(k);
}
catch(std::bad_alloc& e)
{
throw e;
}
if(bRet == true)
{
--sz_EntryCount;
}
return bRet;
}
inline iterator end() const
{
return _EndIt;
}
inline size_t size() const
{
return sz_EntryCount;
}
inline size_t table_size() const
{
return sz_TableSize;
}
inline double current_load_factor() const
{
return d_MultFactor*static_cast<double>(sz_EntryCount);
}
inline double expected_load_factor() const
{
return d_ExpectedLoadFactor;
}
};
}
#endif
.1. operator-> should almost always return a pointer type. When acting as an iterator with value_type T, it should return T*.
In some rarer cases, operator-> may return a different class type, which also has an operator-> member function.
.2. There are no technical requirements on what either form of operator++ must return, but the usual conventions make them act most like the built-in meanings.
class T {
public:
// pre-increment
T& operator++() { increment_me(); return *this; }
// post-increment
T operator++(int) { T copy(*this); increment_me(); return copy; }
//...
};
The built-in meaning of the pre-increment expression ++x first increments the number and then returns an lvalue to the incremented number. A return type of T& acts similarly.
The built-in meaning of the post-increment expression 'x++' increments the variable but returns an rvalue copy of the variable's previous value. So most user-defined overloads return a copy of the original value (which can practically never be a reference).
For an iterator type, how is operator-> overloaded?
It's not. The operator-> can only be overloaded on class types.
If you mean "How do I overload it to return an integer type".
Then the answer is you can't. The result of operator-> is itself de-referenced and as such must be a pointer type (or an object(reference) that is a class type with overload operator->()).
What value should it return assuming that it is an iterator for a collection of class T objects?
It will return a pointer to T
struct Y { int a; };
std::vector<Y> plop(/* DATA TO INIT*/);
std::vector<Y>::iterator b = plop.begin();
b->a = 5; // here b.operator->() returns a pointer to Y object.
// This is then used to access the element `a` of the Y object.
Why does operator++() return by class T& while operator++(int) return by class T?
Technically they can return anything. But usually they are implemented as you suggested.
This is because of the standard implementation of these methods:
class X
{
public:
// Simple one first. The pre-increment just increments the objects state.
// It returns a reference to itself to be used in the expression.
X& operator++()
{
/* Increment this object */
return *this;
}
// Post Increment: This has to increment the current object.
// But the value returned must have the value of the original object.
//
// The easy way to do this is to make a copy (that you return). The copy
// has the original value but now is distinct from this. You can now use
// pre-increment to increment this object and return the copy. Because
// the copy was created locally you can not return by reference.
X operator++(int)
{
X copy(*this);
++(*this);
return copy;
}
};
I understand these two represent prefix increment and postfix increment. But why the difference in return value?
See comments in above code.
operator-> should return a pointer to type T (ie. T*).
Postfix increment has to return a copy of the value, since it performs the increment but before the value has been used. Prefix increment can simply return *this after incrementing.
Simple implementations may look like this:
T T::operator++(int)
{
T temp = *this;
++*this;
return temp;
}
T& T::operator++()
{
this->value += 1;
return *this;
}