vector
deque
list
array
- C++
11forward_list
- C++
11stack
queue
priority_queue
set
map
multiset
multimap
unordered_set
unordered_map
unordered_multiset
unordered_multimap
bitset
vector<bool>
boost::ptr_container
boost::multi_array
boost::intrusive
boost::bimap
boost::circular_buffer
std::vector
Probably 80% or 90% of the time, std::vector
is the
right choice for container.
template <typename T,
typename Allocator = std::allocator<T>>
class vector;
std::vector
push_back
- O(1)*vector
has:
vector
grows by a constant factorpush_back
to be O(1) *push_back
s are O(N)vector
vector
growing invalides all iterators, references or
pointers to elements in the vector, because the storage has
moved and the elements are moved to the new storage
std::vector<Texture> cache;
Texture& myTexture = cache[42];
cache.push_back(Texture(...));
renderer.Draw(myTexture); // oops
reserve
reserve(n)
Sets the capacity to at least n elementsstd::ifstream input("input.txt");
int n;
input >> n;
std::vector<int> numbers;
numbers.reserve(n);
while (n--)
{
int x;
input >> x;
numbers.push_back(x);
}
std::ifstream input("input.txt");
int n;
input >> n;
std::vector<int> numbers(n);
for (int i = 0; i < n; ++i)
{
input >> numbers[i];
}
Still a single allocation
But ...
std::vector<int> numbers(n);
is a call to
vector(size_t count, T value = T())
, i.e.
vector(size_t count, int value = 0)
.
This constructor initializes all the numbers with 0. And we overwrite them immediately.
reserve
doesn't initialize anything.
When elements are loaded one by one it is better to use
reserve
.
void ToVector(int* a, size_t n, std::vector<int>& v)
{
v.resize(n);
// if v.size() < n, allocate storage for n ints
std::copy(a, a + n, v.begin());
}
std::copy
can use std::memcpy
, so it will be
faster
std::vector
almost never frees storagevoid reserve(size_t n) {
if (n > capacity()) {
grow(n);
}
}
void resize(size_t n) {
reserve(n);
if (n > size()) {
// default construct the missing elements
// and set the size to n
for (auto new_end = _begin + n; _end != new_end; ++_end)
{
new (_end) T();
}
} else {
// destruct any extra elements
// and set the size to n
for (auto new_end = _begin + n; _end != new_end; --_end)
{
_end->~T();
}
}
}
void clear() {
resize(0);
}
Sort the sequences in a file
N1 A1_1 A1_2 ... A1_$N1
N2 A2_1 A2_2 ... A2_$N2
...
std::ifstream input("input.txt");
while (input)
{
int n;
input >> n;
std::vector<int> numbers(n);
for (int i = 0; i < n; ++i)
{
input >> numbers[i];
}
std::sort(numbers.begin(), numbers.end());
print_result(numbers);
}
Every iteration `numbers` is destroyed, its
storage is deallocated and allocated againstd::ifstream input("input.txt");
std::vector<int> numbers;
int n;
int x;
while (input)
{
input >> n;
numbers.resize(n);
for (int i = 0; i < n; ++i)
{
input >> numbers[i];
}
std::sort(numbers.begin(), numbers.end());
print_result(numbers);
}
`numbers` will go to the maximum sequence
length in the file and will not allocate againshrinking
a vectorSo how to actually free storage from a vector
?
std::vector<int> v;
std::vector<int> q;
// ...
v.clear();
v.shrink_to_fit();
q.resize(5);
q.shrink_to_fit();
std::vector<int> v;
std::vector<int> q;
// ...
// clear v
std::vector<int>().swap(v);
// shrink q
std::vector<int>(q).swap(q);
vector.erase
Erasing a random element in a vector
, requires all the
elements after the erased to be moved 1 position towards the
front. This makes the erase
to be O(N)
vector.erase
If the order of elements is not important, you can make:
std::vector<A> v;
std::vector<A>::iterator x;
// ...
swap(*x, v.back());
v.pop_back();
vector
and OOPstd::vector<Base> c;
Derived d;
c.push_back(d);
// d just got sliced.
// only the Base part of d gets copied
vector
and OOP{
std::vector<Base*> c;
c.push_back(new Derived);
}
// the Derived object leaked
vector
and OOPvector
is not polymorphic friendlyvector
doesn't take ownership of your pointersvector
Everytime you need a container:
std::array
- C++11Array with constant at compile time size with STL friendly interface.
template <typename T, size_t Size>
class array {
public:
iterator begin();
iterator end();
// ..
private:
T m_Array[Size];
};
std::dynarray
- C++XYThe idea is that if the dynarray
size is small enough,
it storage to be allocated on the stack, instead of on the
heap. This will be much more efficient.
C++ doesn't allow stack allocation, so the proposal says:
deque
Double Ended Queue
push_back
, pop_back
- O(1)*push_front
, pop_front
- O(1)*The cost of growing a deque
is lower than growing a
vector
, because only the collection with pointers to
segments is copied.
list
list
is double-linked listlist.size
splice
in certain caseslist.splice
list
Use list
only if:
std::vector<pointer>
- it will be
more efficient againforward_list
- C++11slist
containerstack
queue
priority_queue
stack
and queue
are not containers, but they adapt (restrict)
the operations over the underlying container to LIFO and FIFO
template <typename T, typename C = std::deque<T>>
class stack;
template <typename T, typename C = std::deque<T>>
class queue;
priority_queue
is a binary heap over a container.
template <typename T,
typename Container = std::vector<T>,
typename Cmp = std::less<typename Container::value_type>
>
class priority_queue;
set
map
multiset
multimap
unordered_set
unordered_map
unordered_multiset
unordered_multimap
Associative containers associate keys with values. Both variants have very similar usage and API, but different implementations and requirements for the types.
template <typename T,
typename Compare = std::less<T>,
typename Allocator = std::allocator<T>
>
class set;
template <typename T,
typename Compare = std::less<T>,
typename Allocator = std::allocator<T>
>
class multiset;
template <typename K,
typename V,
typename Compare = std::less<K>,
typename Allocator = std::allocator<std::pair<const K, V>>
>
class map;
template <typename K,
typename V,
typename Compare = std::less<K>,
typename Allocator = std::allocator<std::pair<const K, V>>
>
class multimap;
multiset
and multimap
allow duplicating of keys.
set
, map
, multiset
, multimap
are ordered because when
iterating the (key, value) pairs in the container, the keys are
orderer by the Compare
comparator.
multiset
or multimap
is undefined
For all orderered associative containers the standard requires:
This means that effectively they are binary search trees. Most implementations are [red-black tree][rbt].
Unordered containers have the following complexities:
This means that they have to be implemented as hash tables.
template <typename T,
typename Hash = std::hash<T>,
typename KeyEqual = std::equal_to<T>,
typename Allocator = std::allocator<T>
>
class unordered_set;
template <typename K,
typename V,
typename Hash = std::hash<K>,
typename KeyEqual = std::less<K>,
typename Allocator = std::allocator<std::pair<const K, V>>
>
class unordered_map;
Generally unordered containers have better performance over ordered, so if the order is not important - use `unordered*`
sorted vector is also an associative container that can provide
both - inordered traversal and better performance over map
and
set
.