From b9f10e7c835a7a3b6f28381d11c76f60903d9ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ion=20Gazta=C3=B1aga?= Date: Sun, 7 Jun 2026 00:12:31 +0200 Subject: [PATCH] Expand devector doc section. --- doc/container.qbk | 33 ++++++++++++++++----- example/doc_devector.cpp | 64 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 example/doc_devector.cpp diff --git a/doc/container.qbk b/doc/container.qbk index 0b61677..f612303 100644 --- a/doc/container.qbk +++ b/doc/container.qbk @@ -432,14 +432,28 @@ erasure times considerably. Flat associative containers have the following attri [section:devector ['devector]] -[classref boost::container::devector devector] is a hybrid of the standard vector and deque containers originally written by Thaler Benedek. -It offers cheap (amortized constant time) insertion at both the front and back ends, -while also providing the regular features of `vector`, in particular the contiguous underlying memory. +[classref boost::container::devector devector] ("double-ended vector") is a hybrid of the standard `vector` and +`deque` containers, originally written by Benedek Thaler. It offers cheap (amortized constant time) insertion at +both the front and back ends, while also providing the regular features of `vector`, in particular contiguous +underlying storage and random access to elements. -Unlike `vector`, [classref boost::container::devector devector] can have free capacity both before and after the elements. This enables efficient -implementation of methods that modify the [classref boost::container::devector devector] at the front. In general, [classref boost::container::devector devector]'s available methods -are a superset of those of `vector` with similar behaviour, barring a couple of iterator invalidation -guarantees that differ. +[*What is it for?] [classref boost::container::devector devector] is the natural choice whenever a program needs a +growable sequence that is frequently modified at [*both] ends but still requires the elements to live in a single +contiguous block of memory. Double-ended queues (work queues, sliding windows, ring-buffer-like patterns) and +parsers or builders that both prepend and append data are typical examples where a plain `vector` would force +expensive shifts at the front and a `deque` would give up contiguity. + +[*Relationship with other containers]. Like `vector`, [classref boost::container::devector devector] keeps all its +elements in a single contiguous buffer, so it offers random access, `data()`-based interoperability with C APIs and +cache-friendly traversal; in fact, [classref boost::container::devector devector]'s available methods are a superset +of those of `vector` with similar behaviour, barring a couple of iterator invalidation guarantees that differ. +Unlike `vector`, it can reserve free capacity [*before] the first element as well as after the last one, which makes +front insertion as cheap as back insertion instead of a linear-time operation. Like `deque`, it provides amortized +constant-time growth at both ends, but unlike `deque` it does not use a segmented layout: the price for contiguity +is that growing past the reserved capacity relocates elements, so iterators and references are invalidated more +often than in a `deque`. In short, [classref boost::container::devector devector] sits between `vector` and `deque`, +trading some of `deque`'s iterator-stability guarantees for `vector`-like contiguous storage with efficient +two-ended growth. The static size overhead for boost's [classref boost::container::devector devector] is one extra `size_t` per container: Usually sizeof([classref boost::container::devector devector]) == 4*sizeof(T*). @@ -484,6 +498,11 @@ However, this strategy has also some downsides: * However the usual container invariant where `size() <= capacity()` does not hold. `size()` can be bigger than `capacity()` because elements can be always inserted at an extreme with free capacity without reallocation. +The following example illustrates the most common operations: efficient insertion and removal at both ends, +random access over contiguous storage and the per-end `reserve_front`/`reserve_back` capacity hints: + +[import ../example/doc_devector.cpp] +[doc_devector] [endsect] diff --git a/example/doc_devector.cpp b/example/doc_devector.cpp new file mode 100644 index 0000000..ef9868b --- /dev/null +++ b/example/doc_devector.cpp @@ -0,0 +1,64 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2024-2024. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// +//[doc_devector +#include + +#include + +//Make sure assertions are active +#ifdef NDEBUG +#undef NDEBUG +#endif +#include + +int main () +{ + using namespace boost::container; + + devector dv; + + //Unlike vector, a devector can grow cheaply (amortized constant time) + //at *both* ends, while keeping all elements in contiguous memory. + dv.push_back(2); // {2} + dv.push_back(3); // {2, 3} + dv.push_front(1); // {1, 2, 3} + dv.push_front(0); // {0, 1, 2, 3} + + assert(dv.size() == 4); + assert(dv.front() == 0); + assert(dv.back() == 3); + + //Random access and contiguous storage, just like vector. + assert(dv[0] == 0 && dv[3] == 3); + const int *p = dv.data(); + for(std::size_t i = 0, n = dv.size(); i != n; ++i) + assert(p[i] == (int)i); + + //We can ask for free capacity at each end independently, so that the + //following insertions at that end do not trigger a reallocation. + dv.reserve_front(dv.size() + 4); + dv.reserve_back(dv.size() + 4); + + //emplace variants construct the element in place at either end. + dv.emplace_front(-1); // {-1, 0, 1, 2, 3} + dv.emplace_back(4); // {-1, 0, 1, 2, 3, 4} + assert(dv.front() == -1); + assert(dv.back() == 4); + + //Removal at both ends is constant time and never reallocates. + dv.pop_front(); // {0, 1, 2, 3, 4} + dv.pop_back(); // {0, 1, 2, 3} + assert(dv.size() == 4); + assert(dv.front() == 0); + assert(dv.back() == 3); + + return 0; +} +//]