Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do custom allocators in STL only allocate the actual data?

Tags:

c++

stl

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?

like image 707
Petter Avatar asked Dec 19 '11 00:12

Petter


1 Answers

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.

like image 146
Kerrek SB Avatar answered Oct 22 '22 10:10

Kerrek SB