...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
Move semantics and placement insertion are two features brought by C++11 containers that can have a very positive impact in your C++ applications. Boost.Container implements both techniques both for C++11 and C++03 compilers.
All containers offered by Boost.Container
can store movable-only types and actual requirements for value_type
depend on each container operations.
Following C++11 requirements even for C++03 compilers, many operations
now require movable or default constructible types instead of just copy
constructible types.
Containers themselves are also movable, with no-throw guarantee if allocator or predicate (if present) copy operations are no-throw. This allows high performance operations when transferring data between vectors. Let's see an example:
#include <boost/container/vector.hpp> #include <boost/move/utility_core.hpp> #include <cassert> //Non-copyable class class non_copyable { BOOST_MOVABLE_BUT_NOT_COPYABLE(non_copyable) public: non_copyable(){} non_copyable(BOOST_RV_REF(non_copyable)) {} non_copyable& operator=(BOOST_RV_REF(non_copyable)) { return *this; } }; int main () { using namespace boost::container; //Store non-copyable objects in a vector vector<non_copyable> v; non_copyable nc; v.push_back(boost::move(nc)); assert(v.size() == 1); //Reserve no longer needs copy-constructible v.reserve(100); assert(v.capacity() >= 100); //This resize overload only needs movable and default constructible v.resize(200); assert(v.size() == 200); //Containers are also movable vector<non_copyable> v_other(boost::move(v)); assert(v_other.size() == 200); assert(v.empty()); return 0; }
All containers offered by Boost.Container implement placement insertion, which means that objects can be built directly into the container from user arguments without creating any temporary object. For compilers without variadic templates support placement insertion is emulated up to a finite (10) number of arguments.
Expensive to move types are perfect candidates emplace functions and in
case of node containers (list
,
set
, ...) emplace allows
storing non-movable and non-copyable types in containers! Let's see an
example:
#include <boost/container/list.hpp> #include <cassert> //Non-copyable and non-movable class class non_copy_movable { non_copy_movable(const non_copy_movable &); non_copy_movable& operator=(const non_copy_movable &); public: non_copy_movable(int = 0) {} }; int main () { using namespace boost::container; //Store non-copyable and non-movable objects in a list list<non_copy_movable> l; non_copy_movable ncm; //A new element will be built calling non_copy_movable(int) constructor l.emplace(l.begin(), 0); assert(l.size() == 1); //A new element will be value initialized l.emplace(l.begin()); assert(l.size() == 2); return 0; }
Incomplete types allow type erasure and recursive data types, and C and C++ programmers have been using it for years to build complex data structures, like tree structures where a node may have an arbitrary number of children.
What about standard containers? Containers of incomplete types have been under discussion for a long time, as explained in Matt Austern's great article (The Standard Librarian: Containers of Incomplete Types):
“Unlike most of my columns, this one is about something you can't do with the C++ Standard library: put incomplete types in one of the standard containers. This column explains why you might want to do this, why the standardization committee banned it even though they knew it was useful, and what you might be able to do to get around the restriction.”
“In 1997, shortly before the C++ Standard was completed, the standardization committee received a query: Is it possible to create standard containers with incomplete types? It took a while for the committee to understand the question. What would such a thing even mean, and why on earth would you ever want to do it? The committee eventually worked it out and came up with an answer to the question. (Just so you don't have to skip ahead to the end, the answer is "no.") But the question is much more interesting than the answer: it points to a useful, and insufficiently discussed, programming technique. The standard library doesn't directly support that technique, but the two can be made to coexist.”
“In a future revision of C++, it might make sense to relax
the restriction on instantiating standard library templates with incomplete
types. Clearly, the general prohibition should stay in place - instantiating
templates with incomplete types is a delicate business, and there are too
many classes in the standard library where it would make no sense. But perhaps
it should be relaxed on a case-by-case basis, and vector
looks like a good candidate for such special-case treatment: it's the one
standard container class where there are good reasons to instantiate it with
an incomplete type and where Standard Library implementors want to make it
work. As of today, in fact, implementors would have to go out of their way
to prohibit it!”
C++11 standard is also cautious about incomplete types and STL: “17.6.4.8 Other functions (...) 2. the effects are undefined in the following cases: (...) In particular - if an incomplete type (3.9) is used as a template argument when instantiating a template component, unless specifically allowed for that component”.
Finally C++17 added support for incomplete types in std::vector
,
std::list
and std::forward_list
(see N4510: Minimal incomplete
type support for standard containers, revision 4 for details),
but no other containers like std::set/map/unordered_set/unordered_map
,
Fortunately all Boost.Container containers
except static_vector
and small_vector
and basic_string
are designed to support incomplete types. static_vector
and small_vector
are special because they statically allocates memory for value_type
and this requires complete types. basic_string
implements Small String Optimization which also requires complete types.
Boost.Container containers supporting incomplete types also support instantiating iterators to those incomplete elements.
Most Boost.Container containers can be used to define recursive containers:
#include <boost/container/vector.hpp> #include <boost/container/stable_vector.hpp> #include <boost/container/deque.hpp> #include <boost/container/list.hpp> #include <boost/container/map.hpp> #include <boost/container/string.hpp> using namespace boost::container; struct data { int i_; //A vector holding still undefined class 'data' vector<data> v_; vector<data>::iterator vi_; //A stable_vector holding still undefined class 'data' stable_vector<data> sv_; stable_vector<data>::iterator svi_; //A stable_vector holding still undefined class 'data' deque<data> d_; deque<data>::iterator di_; //A list holding still undefined 'data' list<data> l_; list<data>::iterator li_; //A map holding still undefined 'data' map<data, data> m_; map<data, data>::iterator mi_; friend bool operator <(const data &l, const data &r) { return l.i_ < r.i_; } }; struct tree_node { string name; string value; //children nodes of this node list<tree_node> children_; list<tree_node>::iterator selected_child_; }; int main() { //a container holding a recursive data type stable_vector<data> sv; sv.resize(100); //Let's build a tree based in //a recursive data type tree_node root; root.name = "root"; root.value = "root_value"; root.children_.resize(7); root.selected_child_ = root.children_.begin(); return 0; }
Containers of incomplete types are useful to break header file dependencies
and improve compilation types. With Boost.Container, you can write a header
file defining a class with containers of incomplete types as data members,
if you carefully put all the implementation details that require knowing
the size of the value_type
in your implementation file:
In this header file we define a class (MyClassHolder)
that holds a vector
of an incomplete type (MyClass
)
that it's only forward declared.
#include <boost/container/vector.hpp> //MyClassHolder.h //We don't need to include "MyClass.h" //to store vector<MyClass> class MyClass; class MyClassHolder { public: void AddNewObject(const MyClass &o); const MyClass & GetLastObject() const; private: ::boost::container::vector<MyClass> vector_; };
Then we can define MyClass
in its own header file.
//MyClass.h class MyClass { private: int value_; public: MyClass(int val = 0) : value_(val){} friend bool operator==(const MyClass &l, const MyClass &r) { return l.value_ == r.value_; } //... };
And include it only in the implementation file of MyClassHolder
//MyClassHolder.cpp #include "MyClassHolder.h" //In the implementation MyClass must be a complete //type so we include the appropriate header #include "MyClass.h" void MyClassHolder::AddNewObject(const MyClass &o) { vector_.push_back(o); } const MyClass & MyClassHolder::GetLastObject() const { return vector_.back(); }
Finally, we can just compile, link, and run!
//Main.cpp #include "MyClassHolder.h" #include "MyClass.h" #include <cassert> int main() { MyClass mc(7); MyClassHolder myclassholder; myclassholder.AddNewObject(mc); return myclassholder.GetLastObject() == mc ? 0 : 1; }
The paper N2913, titled SCARY
Iterator Assignment and Initialization, proposed a requirement that
a standard container's iterator types have no dependency on any type argument
apart from the container's value_type
,
difference_type
, pointer type
,
and const_pointer
type. In
particular, according to the proposal, the types of a standard container's
iterators should not depend on the container's key_compare
,
hasher
, key_equal
,
or allocator
types.
That paper demonstrated that SCARY operations were crucial to the performant implementation of common design patterns using STL components. It showed that implementations that support SCARY operations reduce object code bloat by eliminating redundant specializations of iterator and algorithm templates.
Boost.Container containers implement SCARY
iterators so the iterator type of a container is only dependent on the allocator_traits<allocator_type>::pointer
type (the pointer type of the
value_type
to be inserted
in the container). Reference types and all other typedefs are deduced from
the pointer type using the C++11 pointer_traits
utility. This leads to lower code bloat in algorithms and classes templated
on iterators.
basic_string
,
with an internal buffer of 11/23 bytes (32/64 bit systems) without increasing the usual sizeof
of the string (3 words).
[multi]set/map
containers are size optimized embedding the color bit of the red-black
tree nodes in the parent pointer.
[multi]set/map
containers use no recursive functions so stack problems are avoided.