3

In this code, I get a different sizeof(T) if the allocator is part of a container allocation:

#include <iostream>
#include <set>
#include <cstddef>


class Item
{
    int a;
    unsigned char b, c, d;
    int e, f, g;

  public:
    Item() { a = b = c = d = e = f = g = 0; }
    bool operator<(const Item& item) const { return item.a < a; }
};

template <typename T> class TestAllocator
{
  public:
    typedef T         value_type;
    typedef size_t    size_type;
    typedef ptrdiff_t difference_type;

    typedef T*        pointer;
    typedef const T*  const_pointer;

    typedef T&        reference;
    typedef const T&  const_reference;

    pointer address(reference x) const { return &x; }
    const_pointer address(const_reference x) const { return &x; }

    TestAllocator() { std::cout << "TestAllocator ctor: sizeof T:" << sizeof(T) << std::endl; }

    template <typename U> TestAllocator(const TestAllocator<U>&) {}
    ~TestAllocator() {}

    pointer allocate(size_type /*n*/, void * = 0) { return static_cast<T*>(new T()); }

    void deallocate(pointer p, size_type /*n*/) { delete p; }

    TestAllocator<T>&  operator=(const TestAllocator&) { return *this; }
    void construct(pointer p, const T& val) { new ((T*) p) T(val); }
    void destroy(pointer p) { p->~T(); }
    size_type max_size() const { return size_t(-1); }

    template <typename U> struct rebind { typedef TestAllocator<U> other; };
    template <typename U> TestAllocator& operator=(const TestAllocator<U>&) { return *this; }
};


typedef std::multiset<Item, std::less<Item>, TestAllocator<Item> > ItemMultiset;


int main(int /*argc*/, char** /*argv*/) 
{
  std::cout << "Instantiating allocator:" << std::endl;
  TestAllocator<Item> ta;

  std::cout << "Instantiating container:" << std::endl;
  ItemMultiset ims;

  return 0;
}

Here on my gcc 7.2.1, I get:

Instantiating allocator:
TestAllocator ctor: sizeof T:20
Instantiating container:
TestAllocator ctor: sizeof T:56

Some online compilers results:

VC++ at webcompiler.cloudapp.net said 20 and 36.

Coliru at coliru.stacked-crooked.com said 20 and 56 for all selected gcc compilers, 20 and 56 for clang 3.8, or 20 and 48 for clang 3.8 C++11/14.

Why the difference, and why do some results pad every struct member?

How can I ask what alignment 'mode' the container is in and apply it to my struct or code, or else how can I tell the container to use my code's mode, to ensure the results are always identical?

EDIT: Thanks for the fast reply below.

Wow, a lot of space used. Further results with other containers:

Instantiating allocator:
TestAllocator ctor: sizeof T:20

Instantiating multiset:
TestAllocator ctor: sizeof T:56

Instantiating multimap:
TestAllocator ctor: sizeof T:20

Instantiating list:
TestAllocator ctor: sizeof T:40

Instantiating vector:
TestAllocator ctor: sizeof T:20 

EDIT 2:

For the benefit of those working with allocation pools:

Yay! I think I achieved my goal. The sample code is based on a real app and, as you might expect, the allocator template's allocate and deallocate don't just call new and delete. they hand off to a pool. Until Thursday the pool was a global chunking style multi-dimensional (several different planes for common expected size requests). allocate would pass the number of bytes required. Then I template-ized our global pool, but somewhat clumsily the global instance had to be separately initialized with the desired type - that's where the trouble started, that's not the right type! I saw an opportunity for allocate to pass only the number of items instead of bytes. As you saw it didn't work the way I tried. My mistake was that so soon after template-izing our pool, I didn't realize I could just drop a static instance of it in my allocator template class. Boom, problem solved, all the sizeof's are consistent now. The pool is working fine now, it is now a template embedded into the allocator template class, and it is more lean and efficient than our previous version. ~25 years with C++, templates never cease to amaze me. Thanks for your help.

2 Answers2

4

The multiset doesn't store Items directly, but uses some tree structure that adds additional pointers to navigate in the tree.

It really uses TestAllocator<some_internal_node_type> to allocate objects. And it is the node type's size you get.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • I'd add that this is what the `rebind` member template is for - the container uses that to get from the `TestAllocator` type to the `TestAllocator` it actually uses. – aschepler Oct 28 '17 at 22:35
2

Modifying the display function to:

TestAllocator() { std::cout << "TestAllocator ctor: sizeof T:" << sizeof(T) << " ," << typeid(T).name() << std::endl; }

I get output:

Instantiating allocator:
TestAllocator ctor: sizeof T:20, 4Item
Instantiating container:
TestAllocator ctor: sizeof T:56, St13_Rb_tree_nodeI4ItemE

which should dispel your confusion. The template type used by the multiset is a node class that itself contains an Item. Checking your implementation's multiset header might help with seeing when the allocators are bound and used.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • Apologies for never returning to choose a correct answer. Hope it's not too late. Thanks to both answers, this one is a little more detailed. – MusicMaster Mar 24 '23 at 21:41