I have for homework to make my own abstract class Vector. This vector should have iterator. I make iterator in public part of the Vector. This is my code for iterator:
class iterator {
friend class Vector;
Vector* v_;
int position_;
iterator(Vector* v,int position)
: v_(v),
position_(position)
{}
public:
iterator()
: v_(0),
position_(0)
{}
iterator& operator++() {// pre
position_++;
return *this;
}
iterator operator++(int) {//post
iterator res=*this;
position_++;
return res;
}
T& operator*() {
return v_->buffer_[position_];
}
T* operator->() {
return &buffer_[position_];
}
bool operator==(const iterator& other) const {
return position_ == other.position_;
}
bool operator!=(const iterator& other) const {
return !operator==(other);
}
};
My question is if the operator -> is correct defined.
Thanks
I believe you're really wanting a slightly modified definition that gets the value at the current position of the vector, i.e., return &((*v_)[position_]); if you've overloaded the operator[] of your vector class. Otherwise in order to get access to the buffer_ of your vector, you have to dereference the vector first in order to get to the buffer, i.e., return &(v_->buffer[position_]);
Related
I have a type that has two different implementations, using different data structures. One stores its data in a std::vector<std::unique_ptr<Data>>, the other in a 2D array Data***.
The elements are stored in a specific order, meaning that their position in the vector or 2D array matters. As such, when wanting to iterate over all data in my class, my for loops are dependent on the implementation, being basically one of the following:
for(auto& data : myClass->dataVector) { do Stuff }
for(int x = 0; x < myClass->xVals; x++) {
for(int y = 0; y < myClass->yVals; y++ {
do Stuff with myClass->dataArr[x][y]
}
}
Since the two version of my class share similarities, I want to have a proper parent class that is implemented by two inheriting classes, hopefully in a way that I can iterate over my data by simply doing something such as:
for(auto& data : myClass) { doStuff }
(notice how myClass acts as if it was a collection itself, even if it actually is just a container of a collection)
where the way and order in which this iteration works obviously depends on the implementation of the class.
How do make my class iterable in such a manner?
Lets assume you have a base with all the data, and two derived classes with traversal behavior:
class Base {
public:
std::vector<...> dataVector;
int xVals;
int yVals;
Data** dataArr;
};
Defining .begin() and .end() makes a class iterable with for_each. A simple forwarding to the vector iterators is enough for the first case:
class DerivedA : private Base {
public:
auto begin() { return this->dataVector.begin(); }
auto begin() const { return this->dataVector.begin(); }
auto end() { return this->dataVector.end(); }
auto end() const { return this->dataVector.end(); }
}
For the Data** case you will have to define a custom iterator:
class iterator {
public:
using value_type = Data;
using difference_type = std::ptrdiff_t;
using reference = Data&;
using pointer = Data*;
using iterator_category = std::forward_iterator_tag;
iterator() : m_base(), m_idx(0) { }
iterator(Base* b, std::size_t idx) : m_base(b), m_idx(idx) { }
reference operator*() const { return m_base->dataArr[m_idx / m_base->yVals][m_idx % m_base->y_vals]; }
pointer operator->() const { return &**this; }
friend iterator& operator++(iterator& rhs) { ++rhs.m_idx; return rhs; }
friend iterator operator++(iterator& lhs, int) { auto cp = lhs; ++lhs; return cp; }
friend bool operator==(iterator lhs, iterator rhs) { return lhs.m_idx == rhs.m_idx; }
friend bool operator!=(iterator lhs, iterator rhs) { return !(lhs == rhs); }
private:
Base* m_base;
std::size_t m_idx;
};
class const_iterator {
// equivalent but const. (reference = const Data& and pointer = const Data*)
// Make sure iterator is convertible to const_iterator.
};
class DerivedB : private Base {
iterator begin() { return { this, 0 }; }
const_iterator begin() const { return { this, 0 }; }
iterator end() { return { this, this->xVals*this->yVals }; }
const_iterator end() const { return { this, this->xVals*this->yVals }; }
};
I am trying to handle std::list of pointer type, like this:
std::list<int*> pNums;
Originally, iterating this container with range-based for loop will look like this :
for(int* pNum : pNums)
{
std::cout << (*pNum) << std::endl;
}
However, I want to iterate this container with a value, not a pointer, like below:
for(int num : Range(pNums))
{
std::cout << num << std::endl;
}
|
Here, Range is a custom wrapping-class of std::list<int*>, something should be defined in this manner, I guess:
class Range
{
Range(std::list<int*>& _list) : list(_list) {}
std::list<int*>& list;
// Basically inherit the original iterator
class custom_const_iterator : std::list<int*>::const_iterator
{
// Define an overloaded dereference operator
const int& operator*() const
{
...
}
...
};
public:
custom_const_iterator begin() { return ...; }
custom_const_iterator end() { return ...; }
};
So, my question is, what should I write down for class Range?
I would take the following approach (explanation in the comments):
class Range
{
private:
// Store iterators to the begin and the end of the range,
// rather than a reference to the whole list
std::list<int*>::const_iterator first;
std::list<int*>::const_iterator last;
class iterator
{
private:
std::list<int*>::const_iterator it;
public:
explicit iterator(std::list<int*>::const_iterator i) : it(i) {}
// you should define all the other operators
// that a std::list iterator has!
iterator& operator++()
{
++it;
return *this;
}
iterator operator++(int)
{
it++;
return *this;
}
// just dereference to get the value
const int& operator*() const { return **it; }
// these two are quite important for basic functionality
bool operator==(const iterator& rhs) const { return it == rhs.it; }
bool operator!=(const iterator& rhs) const { return it != rhs.it; }
};
public:
Range(std::list<int*>& _list)
: first(_list.begin()), last(_list.end())
{}
public:
iterator begin() { return iterator(first); }
iterator end() { return iterator(last); }
};
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 have 2 classes:
class iterator {
Node<K,V>* n;
public:
iterator(){
std::cout<<"new empty iterator"<<std::endl;
};
iterator(const iterator& iter):n(iter.n){
std::cout<<"copy iterator"<<std::endl;
}
explicit iterator(Node<K,V>* nodePtr):n(nodePtr) {
std::cout<<"create iterator with Node Ptr"<<std::endl;
}
iterator& operator=(const iterator& iter);
void operator++();
Node<K,V>& operator*();
bool operator!=(const iterator& iter);
K& operator[](const Key& k)const;
V& operator[](const Val& v)const;
Node<K,V>* getNodePtr()const{
std::cout<<"get Node Ptr"<<std::endl;
return n;
}
friend class Map;
};
class const_iterator :public iterator {
const Node<K,V>* n;
public:
const_iterator(){
std::cout<<"new empty const_iterator"<<std::endl;
};
const_iterator(const iterator& iter):
n(iter.getNodePtr()){
std::cout<<"convert"<<std::endl;
}
const_iterator(const const_iterator& iter):n(iter.n){}
explicit const_iterator(const Node<K,V>* node):n(node){}
const_iterator& operator=(const const_iterator& iter){
n = iter.n;
return *this;
}
const Node<K,V>& operator*(){
return *n;
}
friend class Map;
};
iterator begin()const{
return iterator(firstKey);
}
iterator end()const{
return iterator(dummyKey);
}
I want it to make an automatic conversion between the 2 classes using:
const_iterator(const iterator& iter):
n(iter.getNodePtr()){
std::cout<<"convert"<<std::endl;
}
to do something like this for example:
Map<int,int>::const_iterator it = m.begin();
now, the thing is, that this constructor calls for some reason iterator(); in the first class and I can't figure out why.
I know begin() and end() have some standart versions, but I can't use any of it here. I also can't make Map to be const, or write a conversion function.
any help?
You've made iterator a base class of const_iterator, so an iterator constructor will (and should) be called as part of constructing a const_iterator.
If you will define data storage as
const Map<int, int> data;
instead of
Map<int, int> data;
const version of begin() and end() will be invoked.
Also, C++11 standard containers add cbegin() and cend() for that purpose. You could re-difine it as follows:
const_iterator cbegin()const{
return const_iterator(firstKey);
}
const_iterator cend()const{
return const_iterator(dummyKey);
}
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.