Let's say I create a linked list in STL:
list<int, my_allocator<int> > data;
Then I can use a more efficient allocator, let's say a memory pool. But doesn't the list need to allocate internal memory like forward and backward pointers to traverse the list? How will these be allocated? Using normal new
or somehow using my_allocator
?
The container does indeed repurpose your allocator to allocate its own book-keeping material. (Not that it would matter for a std::list
, but it's true in general.*) This is why the standard allocator requirements mandate the existence of the rebind
template:
typedef typename Alloc::template rebind<MyInternalStuff>::other internal_allocator;
If your allocator is Alloc = my_allocator<T>
, then internal_allocator
becomes my_allocator<MyInternalStuff>
.
I believe that this was one of the gripes that Electronic Arts had with the C++ standard library, which is why their EASTL library uses a different convention for allocators that offers tighter control.
*) Typically, each node will be one monolithic object of some type Node<T>
, so I suppose std::list<T, Alloc>
only ever uses Alloc::rebind<Node<T>>::other
as an allocator.
[Sorry for the multiple edits; I had the output mangled up and didn't interpret it correctly; I now went and printed each container separately and fixed the output up accordingly. std::list
does indeed only require one allocator.]
Update: Just for giggles, I wrote a little demangling-allocator which prints its own typename upon construction. Here's the input:
#include <unordered_map>
#include <set>
#include <deque>
#include <list>
#include <vector>
#include <map>
#include <iostream>
int main()
{
std::cout << "----- unordered_map<int, double> -----------" << std::endl;
std::unordered_map<int, double, std::hash<int>, std::equal_to<int>, funky_allocator<std::pair<const int, double>>> m { {1, 1.2} };
std::cout << "----- set<int> -----------------------------" << std::endl;
std::set<int, std::less<int>, funky_allocator<int>> s;
std::cout << "----- deque<int> ---------------------------" << std::endl;
std::deque<int, funky_allocator<int>> d;
std::cout << "----- list<int> ----------------------------" << std::endl;
std::list<int, funky_allocator<int>> l;
std::cout << "----- vector<int> --------------------------" << std::endl;
std::vector<int, funky_allocator<int>> c;
std::cout << "----- map<int, bool> -----------------------" << std::endl;
std::map<int, bool, std::less<int>, funky_allocator<std::pair<const int, bool>>> n { { 1, true } };
}
And here the output:
----- unordered_map<int, double> -----------
Default-construct: funky_allocator<std::pair<int const, double> >
Copy-construct: funky_allocator<std::__detail::_Hash_node<std::pair<int const, double>, false> >
Copy-construct: funky_allocator<std::__detail::_Hash_node<std::pair<int const, double>, false>*>
----- set<int> -----------------------------
Default-construct: funky_allocator<std::_Rb_tree_node<int> >
----- deque<int> ---------------------------
Default-construct: funky_allocator<int>
Copy-construct: funky_allocator<int*>
----- list<int> ----------------------------
Default-construct: funky_allocator<std::_List_node<int> >
----- vector<int> --------------------------
Default-construct: funky_allocator<int>
----- map<int, bool> -----------------------
Default-construct: funky_allocator<std::_Rb_tree_node<std::pair<int const, bool> > >
The details vary depending on which constructor is used: Containers like set
and map
might only construct the "correct" allocator in some invocation, while in another they may construct an object of the specified allocator first. Either way, the specified allocator never gets used at all for a couple of containers, and only the rebound version is used.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With