Home | Libraries | People | FAQ | More |
The document C++ Extensions for Library Fundamentals (Final draft: N4480) includes classes that provide allocator type erasure and runtime polymorphism. As Pablo Halpern, the author of the proposal, explains in the paper (N3916 Polymorphic Memory Resources (r2)):
“A significant impediment to effective memory management in C++ has been the inability to use allocators in non-generic contexts. In large software systems, most of the application program consists of non-generic procedural or object-oriented code that is compiled once and linked many times.”
“Allocators in C++, however, have historically relied solely on compile-time polymorphism, and therefore have not been suitable for use in vocabulary types, which are passed through interfaces between separately-compiled modules, because the allocator type necessarily affects the type of the object that uses it. This proposal builds upon the improvements made to allocators in C++11 and describes a set of facilities for runtime polymorphic memory resources that interoperate with the existing compile-time polymorphic allocators.”
Boost.Container implements nearly all classes
of the proposal under the namespace boost::container::pmr
. There
are two groups,
polymorphic_allocator
.
monotonic_buffer_resource
.
unsynchronized_pool_resource
.
synchronized_pool_resource
.
get_default_resource
/
set_default_resource
/
new_delete_resource
/
null_memory_resource
pmr::vector
,
etc.)
Boost.Container's polymorphic resource library is usable from C++03 containers, and offers some alternative utilities if the required C++11 features of the Library Fundamentals specification are not available.
Let's review the usage example given in N3916 and see how it can be implemented using Boost.Container: Suppose we are processing a series of shopping lists, where a shopping list is a container of strings, and storing them in a collection (a list) of shopping lists. Each shopping list being processed uses a bounded amount of memory that is needed for a short period of time, while the collection of shopping lists uses an unbounded amount of memory and will exist for a longer period of time. For efficiency, we can use a more time-efficient memory allocator based on a finite buffer for the temporary shopping lists.
Let's see how ShoppingList
can be defined to support an polymorphic memory resource that can allocate
memory from different underlying mechanisms. The most important details are:
allocator_type
typedef. This allocator_type
will be of type memory_resource *
,
which is a base class for polymorphic resources.
ShoppingList
has
constructors taking memory_resource*
as the last argument.
ShoppingList
has
constructors taking allocator_arg_t
as the first argument and memory_resource*
as the second argument.
Note: In C++03 compilers, it is
required that the programmer specializes as true
constructible_with_allocator_suffix
or constructible_with_allocator_prefix
as in C++03 there is no way to automatically detect the chosen option at compile
time. If no specialization is done, Boost.Container
assumes the suffix option.
//ShoppingList.hpp #include <boost/container/pmr/vector.hpp> #include <boost/container/pmr/string.hpp> class ShoppingList { // A vector of strings using polymorphic allocators. Every element // of the vector will use the same allocator as the vector itself. boost::container::pmr::vector_of <boost::container::pmr::string>::type m_strvec; //Alternatively in compilers that support template aliases: // boost::container::pmr::vector<boost::container::pmr::string> m_strvec; public: // This makes uses_allocator<ShoppingList, memory_resource*>::value true typedef boost::container::pmr::memory_resource* allocator_type; // If the allocator is not specified, "m_strvec" uses pmr::get_default_resource(). explicit ShoppingList(allocator_type alloc = 0) : m_strvec(alloc) {} // Copy constructor. As allocator is not specified, // "m_strvec" uses pmr::get_default_resource(). ShoppingList(const ShoppingList& other) : m_strvec(other.m_strvec) {} // Copy construct using the given memory_resource. ShoppingList(const ShoppingList& other, allocator_type a) : m_strvec(other.m_strvec, a) {} allocator_type get_allocator() const { return m_strvec.get_allocator().resource(); } void add_item(const char *item) { m_strvec.emplace_back(item); } //... };
However, this time-efficient allocator is not appropriate for the longer lived collection of shopping lists. This example shows how those temporary shopping lists, using a time-efficient allocator, can be used to populate the long lived collection of shopping lists, using a general purpose allocator, something that would be annoyingly difficult without the polymorphic allocators.
In Boost.Container for the time-efficient
allocation we can use monotonic_buffer_resource
,
providing an external buffer that will be used until it's exhausted. In the
default configuration, when the buffer is exhausted, the default memory resource
will be used instead.
#include "ShoppingList.hpp" #include <cassert> #include <boost/container/pmr/list.hpp> #include <boost/container/pmr/monotonic_buffer_resource.hpp> void processShoppingList(const ShoppingList&) { /**/ } int main() { using namespace boost::container; //All memory needed by folder and its contained objects will //be allocated from the default memory resource (usually new/delete) pmr::list_of<ShoppingList>::type folder; // Default allocator resource //Alternatively in compilers that support template aliases: // boost::container::pmr::list<ShoppingList> folder; { char buffer[1024]; pmr::monotonic_buffer_resource buf_rsrc(&buffer, 1024); //All memory needed by temporaryShoppingList will be allocated //from the local buffer (speeds up "processShoppingList") ShoppingList temporaryShoppingList(&buf_rsrc); assert(&buf_rsrc == temporaryShoppingList.get_allocator()); //list nodes, and strings "salt" and "pepper" will be allocated //in the stack thanks to "monotonic_buffer_resource". temporaryShoppingList.add_item("salt"); temporaryShoppingList.add_item("pepper"); //... //All modifications and additions to "temporaryShoppingList" //will use memory from "buffer" until it's exhausted. processShoppingList(temporaryShoppingList); //Processing done, now insert it in "folder", //which uses the default memory resource folder.push_back(temporaryShoppingList); assert(pmr::get_default_resource() == folder.back().get_allocator()); //temporaryShoppingList, buf_rsrc, and buffer go out of scope } return 0; }
Notice that the shopping lists within folder
use the default allocator resource whereas the shopping list temporaryShoppingList
uses the short-lived
but very fast buf_rsrc
. Despite
using different allocators, you can insert temporaryShoppingList
into folder because they have the same ShoppingList
type. Also, while ShoppingList
uses memory_resource directly, pmr::list
,
pmr::vector
and
pmr::string
all
use polymorphic_allocator
.
The resource passed to the ShoppingList
constructor is propagated to the vector and each string within that ShoppingList
. Similarly, the resource used
to construct folder
is propagated
to the constructors of the ShoppingLists that are inserted into the list (and
to the strings within those ShoppingLists
).
The polymorphic_allocator
template is designed to be almost interchangeable with a pointer to memory_resource
, thus
producing a bridge between the template-policy style of
allocator and the polymorphic-base-class style of allocator.
This example actually shows how easy is to use Boost.Container to write type-erasured allocator-capable classes even in C++03 compilers.