Sometimes we need a light-weight vector-like container in our applications. The array class in the Boost library is a good candidate for such purposes. However, since the size of vector is fixed at the compile time, sometimes it is difficult to use it together with generic algorithms.
For example, in the adjacency_list class in the Boost Graph Library (BGL), if one can replace the container for out-edges (std::vector by default) to a light-weight vector like array, it is expected that overhead in memory allocation will be reduced greatly. Actually, in most applications (at least in our research field), the maximum number of out-edges in a graph is strictly limited to a certain small integer of O(1)-O(10). In such cases we could fix the capacity of the container safely at compile time. However, clearly the array class can not be used instead of std::vector in this case, since the number of out-edges can change if we add/remove edges to/from a graph. Similar situations would be commonly observed in many applications in many fields.
The fixed_capacity_vector is a kind of sequence that, like a std::vector, supports random access iterators, constant time insert and erase operations at the end, etc. It could be used almost everywhere instead of std::vector, except that its capacity is fixed to a (small) constant integer determined through the template parameter at compile time. Unlike std::vector, it does not rely on dynamic memory allocation at all. Instead, it has a static array as its member. In some applications, this tiny trick could have a great impact on performance (and possibly on space).
The fixed_capacity_deque, which is based on the same concept as the fixed_capacity_vector, is an alternative of std::deque. In addition, this library also provides a traits class fixed_capacity_traits.
Use a standard STL container (std::vector, etc) as long as possible. The standard STL containers are (expected to be) well-tested, exception-safe, and efficient enough for most applications. Use a fixed-capacity container, if and only if the efficiency or the space of the container is crucial for your application, and you are sure of the maximum number of elements.
namespace alps { namespace fixed_capacity { struct no_checking; struct capacity_checking; struct strict_checking; } template<class T, std::size_t N, class CheckingPolicy = fixed_capacity::no_checking> class fixed_capacity_vector; template<class T, std::size_t N> bool operator==(const fixed_capacity_vector<T,N>& x, const fixed_capacity_vector<T,N>& y); template<class T, std::size_t N> bool operator< (const fixed_capacity_vector<T,N>& x, const fixed_capacity_vector<T,N>& y); template<class T, std::size_t N> bool operator!=(const fixed_capacity_vector<T,N>& x, const fixed_capacity_vector<T,N>& y); template<class T, std::size_t N> bool operator> (const fixed_capacity_vector<T,N>& x, const fixed_capacity_vector<T,N>& y); template<class T, std::size_t N> bool operator<=(const fixed_capacity_vector<T,N>& x, const fixed_capacity_vector<T,N>& y); template<class T, std::size_t N> bool operator>=(const fixed_capacity_vector<T,N>& x, const fixed_capacity_vector<T,N>& y); template<class T, std::size_t N> void swap(fixed_capacity_vector<T,N>& x, fixed_capacity_vector<T,N>& y); template<class T, std::size_t N> }
namespace alps { namespace fixed_capacity { struct no_checking; struct capacity_checking; struct strict_checking; } template<class T, std::size_t N, class CheckingPolicy = fixed_capacity::no_checking> class fixed_capacity_deque; template<class T, std::size_t N> bool operator==(const fixed_capacity_deque<T,N>& x, const fixed_capacity_deque<T,N>& y); template<class T, std::size_t N> bool operator< (const fixed_capacity_deque<T,N>& x, const fixed_capacity_deque<T,N>& y); template<class T, std::size_t N> bool operator!=(const fixed_capacity_deque<T,N>& x, const fixed_capacity_deque<T,N>& y); template<class T, std::size_t N> bool operator> (const fixed_capacity_deque<T,N>& x, const fixed_capacity_deque<T,N>& y); template<class T, std::size_t N> bool operator<=(const fixed_capacity_deque<T,N>& x, const fixed_capacity_deque<T,N>& y); template<class T, std::size_t N> bool operator>=(const fixed_capacity_deque<T,N>& x, const fixed_capacity_deque<T,N>& y); template<class T, std::size_t N> void swap(fixed_capacity_deque<T,N>& x, fixed_capacity_deque<T,N>& y); }
namespace alps { template<class T> class fixed_capacity_traits; }
The fixed-capacity containers have almost the equivalent semantics as the corresponding standard containers. For details see the C++ Standard (Sections 23.2.4 [lib.vector] and 23.2.1 [lib.deque]).
The fixed_capcity_vector and fixed_capacity_deque do not provide get_allocator() member function, since they have no allocator.
A fixed-capacity container provides a compile-time constant:
static const std::size_t static_max_size;which value is equal to its capacity, i.e. the second template parameter N. In addition, the capacity() (in fixed_capacity_vector) and max_size() (in fixed_capacity_vector and fixed_capacity_deque) member functions are declared as static.
All the member functions should be the same complexity as those of the corresponding standard containers, except that the swap() member function as well as the global swap() function has a linear complexity (not a constant time) unlike the standard ones.
By default the member functions of the fixed-capacity containers do not check the validity of arguments (indices and iterators) and they do not throw any exception by themselves. For the compatibility with std::vector and std::deque at() member function is still remained, but it is completely equivalent to operator[] (i.e. never throws even for out-of-bound access). This default checking policy can be modified by specifying a policy class as the third template parameter (CheckingPolicy).
Policy Class |
Description |
||
::alps::fixed_capacity::no_checking |
Default checking policy. No checking even in at() member function. | ||
::alps::fixed_capacity::capacity_checking |
The validity of indices passed to operator[] and at() is checked. In addition if one try to add elements more than the fixed capacity by insert(), push_back(), etc, an exception will be thrown. | ||
::alps::fixed_capacity::strict_checking |
In addition to capacity_checking the validity of iterator is checked in insert(), erase(), and others. |
If a check fails at run time, the std::range_error exception will be thrown.
A traits class fixed_capacity_traits<T> (defined in the header <alps/fixed_capacity_traits.h> provides a static bool constant "capacity_is_fixed", which takes true if the capacity of container class T is fixed. In addition, if T is a fixed-capacity container (i.e. capacity_is_fixed = true), it also provides a static integer constant "static_max_size", which value is equal to the capacity of class T.
The header <alps/fixed_capacity_fwd.h> provides forward declarations of fixed_capacity_vector, fixed_capacity_deque, and fixed_capacity_traits.
I would like to thank Matthias Troyer, Darin Adler, Andrei Alexandrescu and Ralf Grosse-Kunstleve for helpful suggestions and comments.
Distributed under the Boost Software License, Version 1.0. (See http://www.boost.org/LICENSE_1_0.txtå)