Are there guidelines on how one should write new container which will behave like any STL
container?
-
8See the implementations of the existing standard containers, and try to understand them - the functions, the return types, operator overloads, nested types, memory management and all. – Nawaz Oct 13 '11 at 18:19
-
I usually start by copying the member function prototypes of whichever container is closest in concept to what I'm doing, either from msdn, or the standard. (http://www.cplusplus.com does not have C++11 functions, and www.sgi.com doesn't match) – Mooing Duck Oct 13 '11 at 18:28
-
@Mooing Duck: you think msdn is closer to the standard than sgi? – Daniel Oct 13 '11 at 18:36
-
3It definitely is. MSDN is current - SGI is pre-Standard – Puppy Oct 13 '11 at 18:50
-
@Dani: I (mistakenly) thought that the stuff I'd seen at cplusplus.com was from SGI. I don't know how good SGI is, but they don't have any C++11 stuff I see. MSDN does. – Mooing Duck Oct 13 '11 at 19:02
-
See complexity guarantees for the different container types. http://stackoverflow.com/questions/181693/what-are-the-complexity-guarantees-of-the-standard-containers – Martin York Oct 13 '11 at 19:05
-
@MooingDuck: cplusplus.com is notoriously erroneous,there was a thread around here,which pointed out the errors,you may want to lookup for it. – Alok Save Oct 13 '11 at 19:14
-
10The best online reference (wrt completeness, correctness and especially usability) is by far cppreference.com. It also explains a ton of language features aside from the library. And it's a wiki, so it should contain less errors than cplusplus.com. – rubenvb Aug 20 '13 at 17:47
3 Answers
Here's a sequence pseudo-container I pieced together from § 23.2.1\4 Note that the iterator_category
should be one of std::input_iterator_tag
, std::output_iterator_tag
,std::forward_iterator_tag
,std::bidirectional_iterator_tag
,std::random_access_iterator_tag
. Also note that the below is technically more strict than required, but this is the idea. Note that the vast majority of the "standard" functions are technically optional, due to the awesomeness that is iterators.
template <class T, class A = std::allocator<T> >
class X {
public:
typedef A allocator_type;
typedef typename A::value_type value_type;
typedef typename A::reference reference;
typedef typename A::const_reference const_reference;
typedef typename A::difference_type difference_type;
typedef typename A::size_type size_type;
class iterator {
public:
typedef typename A::difference_type difference_type;
typedef typename A::value_type value_type;
typedef typename A::reference reference;
typedef typename A::pointer pointer;
typedef std::random_access_iterator_tag iterator_category; //or another tag
iterator();
iterator(const iterator&);
~iterator();
iterator& operator=(const iterator&);
bool operator==(const iterator&) const;
bool operator!=(const iterator&) const;
bool operator<(const iterator&) const; //optional
bool operator>(const iterator&) const; //optional
bool operator<=(const iterator&) const; //optional
bool operator>=(const iterator&) const; //optional
iterator& operator++();
iterator operator++(int); //optional
iterator& operator--(); //optional
iterator operator--(int); //optional
iterator& operator+=(size_type); //optional
iterator operator+(size_type) const; //optional
friend iterator operator+(size_type, const iterator&); //optional
iterator& operator-=(size_type); //optional
iterator operator-(size_type) const; //optional
difference_type operator-(iterator) const; //optional
reference operator*() const;
pointer operator->() const;
reference operator[](size_type) const; //optional
};
class const_iterator {
public:
typedef typename A::difference_type difference_type;
typedef typename A::value_type value_type;
typedef typename const A::reference reference;
typedef typename const A::pointer pointer;
typedef std::random_access_iterator_tag iterator_category; //or another tag
const_iterator ();
const_iterator (const const_iterator&);
const_iterator (const iterator&);
~const_iterator();
const_iterator& operator=(const const_iterator&);
bool operator==(const const_iterator&) const;
bool operator!=(const const_iterator&) const;
bool operator<(const const_iterator&) const; //optional
bool operator>(const const_iterator&) const; //optional
bool operator<=(const const_iterator&) const; //optional
bool operator>=(const const_iterator&) const; //optional
const_iterator& operator++();
const_iterator operator++(int); //optional
const_iterator& operator--(); //optional
const_iterator operator--(int); //optional
const_iterator& operator+=(size_type); //optional
const_iterator operator+(size_type) const; //optional
friend const_iterator operator+(size_type, const const_iterator&); //optional
const_iterator& operator-=(size_type); //optional
const_iterator operator-(size_type) const; //optional
difference_type operator-(const_iterator) const; //optional
reference operator*() const;
pointer operator->() const;
reference operator[](size_type) const; //optional
};
typedef std::reverse_iterator<iterator> reverse_iterator; //optional
typedef std::reverse_iterator<const_iterator> const_reverse_iterator; //optional
X();
X(const X&);
~X();
X& operator=(const X&);
bool operator==(const X&) const;
bool operator!=(const X&) const;
bool operator<(const X&) const; //optional
bool operator>(const X&) const; //optional
bool operator<=(const X&) const; //optional
bool operator>=(const X&) const; //optional
iterator begin();
const_iterator begin() const;
const_iterator cbegin() const;
iterator end();
const_iterator end() const;
const_iterator cend() const;
reverse_iterator rbegin(); //optional
const_reverse_iterator rbegin() const; //optional
const_reverse_iterator crbegin() const; //optional
reverse_iterator rend(); //optional
const_reverse_iterator rend() const; //optional
const_reverse_iterator crend() const; //optional
reference front(); //optional
const_reference front() const; //optional
reference back(); //optional
const_reference back() const; //optional
template<class ...Args>
void emplace_front(Args&&...); //optional
template<class ...Args>
void emplace_back(Args&&...); //optional
void push_front(const T&); //optional
void push_front(T&&); //optional
void push_back(const T&); //optional
void push_back(T&&); //optional
void pop_front(); //optional
void pop_back(); //optional
reference operator[](size_type); //optional
const_reference operator[](size_type) const; //optional
reference at(size_type); //optional
const_reference at(size_type) const; //optional
template<class ...Args>
iterator emplace(const_iterator, Args&&...); //optional
iterator insert(const_iterator, const T&); //optional
iterator insert(const_iterator, T&&); //optional
iterator insert(const_iterator, size_type, T&); //optional
template<class iter>
iterator insert(const_iterator, iter, iter); //optional
iterator insert(const_iterator, std::initializer_list<T>); //optional
iterator erase(const_iterator); //optional
iterator erase(const_iterator, const_iterator); //optional
void clear(); //optional
template<class iter>
void assign(iter, iter); //optional
void assign(std::initializer_list<T>); //optional
void assign(size_type, const T&); //optional
void swap(X&);
size_type size() const;
size_type max_size() const;
bool empty() const;
A get_allocator() const; //optional
};
template <class T, class A = std::allocator<T> >
void swap(X<T,A>&, X<T,A>&); //optional
Also, whenever I make a container, I test with a class more or less like this:
#include <cassert>
struct verify;
class tester {
friend verify;
static int livecount;
const tester* self;
public:
tester() :self(this) {++livecount;}
tester(const tester&) :self(this) {++livecount;}
~tester() {assert(self==this);--livecount;}
tester& operator=(const tester& b) {
assert(self==this && b.self == &b);
return *this;
}
void cfunction() const {assert(self==this);}
void mfunction() {assert(self==this);}
};
int tester::livecount=0;
struct verify {
~verify() {assert(tester::livecount==0);}
}verifier;
Make containers of tester
objects, and call each one's function()
as you test your container. Do not make any global tester
objects. If your container cheats anywhere, this tester
class will assert
and you'll know that you cheated accidentally somewhere.

- 64,318
- 19
- 100
- 158
-
1This is interesting. How does your tester work? There are several parse errors, which are trivial (missing ';') but not sure how that verify destructor works. Oh, you meant `assert(tester::livecount == 0);`. Mmmmm, still not sure how this tester framework works. Could you give an example? – Adrian Dec 24 '14 at 19:17
-
2The tester has a single nonstatic member that's a pointer to itself, and the destructor and members are a way to check that no invalid `memcpy` happened. (test isn't foolproof, but it catches some). The `livecount` is a simple leak detector, to make sure your container called an equal number of constructors and destructors. – Mooing Duck Dec 24 '14 at 20:45
-
Ok, I see that, but how does that test your iterator? BTW, I think you meant `verifier` not `varifier`. – Adrian Dec 24 '14 at 21:14
-
4@Adrian No no, you write your container, and then put a bunch of these in the container, and do things with the container, to verify that you didn't accidentally memcpy, and rememebered to call all the destructors. – Mooing Duck Dec 24 '14 at 22:12
-
1may I suggest inheriting the iterator from `std::iterator` from the header `
` – sp2danny Dec 13 '15 at 14:54 -
I think `size`, `max_size`, `empty`, and `get_allocator` should be const. – Trevor Sundberg Jun 22 '17 at 21:58
-
Also I believe the typedefs on the const_iterator should use `const_reference` and `const_pointer` off of `allocator`. I've been using this to implement a full STL styled container... fantastic work by the way! – Trevor Sundberg Jun 24 '17 at 23:50
-
@Trevor: I'm pretty sure that's backwards. The allocator should be using these typedefs. The iterator shouldn't know or care about allocators. Actually, allocators shouldn't know about iterators either. I'm curious why you say that's needed. – Mooing Duck Jun 26 '17 at 18:39
-
I'm just going off your example, inside the const_iterator you wrote: `typedef typename A::reference const_reference;`. This is problematic because it makes all the `const_reference` return a non-const reference. It should at least be `typedef typename A::const_reference const_reference;`... right? I don't know anything about whether or not that typedef should even be there... it's in your example. – Trevor Sundberg Jun 27 '17 at 20:20
-
@Trevor: Looking closer, you're right. `reference` and `pointer` were both misnamed and incorrectly const qualified. Fixed. – Mooing Duck Jun 27 '17 at 20:54
You will need to read the C++ Standard section about Containers and requirements the C++ Standard imposes for container implementations.
The relevant chapter in C++03 standard is:
Section 23.1 Container Requirements
The relevant chapter in C++11 standard is:
Section 23.2 Container Requirements
The near-final draft of the C++11 standard is freely available here.
You might as well, read some excellent books which will help you understand the requirements from an perspective of user of the container. Two excellent books which struck my mind easily are:
Effective STL by Scott Meyers &
The C++ Standard Library: A Tutorial and Reference by Nicolai Josutils

- 202,538
- 53
- 430
- 533
Here is a very simplistic implementation of a fake vector, which is basically a wrapper around std::vector
and has its own (but real) iterator, which mimics the STL iterator. Again, the iterator is very simplistic, skipping over many concepts like const_iterator
, validity checks etc.
Code is runnable out of the box.
#include <iostream>
#include <string>
#include <vector>
template<typename T>
struct It
{
std::vector<T>& vec_;
int pointer_;
It(std::vector<T>& vec) : vec_{vec}, pointer_{0} {}
It(std::vector<T>& vec, int size) : vec_{vec}, pointer_{size} {}
bool operator!=(const It<T>& other) const
{
return !(*this == other);
}
bool operator==(const It<T>& other) const
{
return pointer_ == other.pointer_;
}
It& operator++()
{
++pointer_;
return *this;
}
T& operator*() const
{
return vec_.at(pointer_);
}
};
template<typename T>
struct Vector
{
std::vector<T> vec_;
void push_back(T item)
{
vec_.push_back(item);
};
It<T> begin()
{
return It<T>(vec_);
}
It<T> end()
{
return It<T>(vec_, vec_.size());
}
};
int main()
{
Vector<int> vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
bool first = true;
for (It<int> it = vec.begin(); it != vec.end(); ++it)
{
if (first) //modify container once while iterating
{
vec.push_back(4);
first = false;
}
std::cout << *it << '\n'; //print it
(*it)++; //change it
}
for (It<int> it = vec.begin(); it != vec.end(); ++it)
{
std::cout << *it << '\n'; //should see changed value
}
}

- 2,479
- 1
- 20
- 26