Почему sizeof(T) внутри моего контейнерного распределителя отличается от только в нем?

В этом коде я получаю другое значение sizeof(T), если распределитель является частью выделения контейнера:

#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;
}

Здесь на моем gcc 7.2.1 я получаю:

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

Результаты некоторых онлайн-компиляторов:

VC++ на webcompiler.cloudapp.net сказал 20 и 36.

Колиру на coliru.stacked-crooked.com сказал 20 и 56 для всех выбранных компиляторов gcc, 20 и 56 для clang 3.8 или 20 и 48 для clang 3.8 C++ 11/14.

В чем разница и почему некоторые результаты дополняют каждый член структуры?

Как я могу узнать, в каком «режиме» выравнивания находится контейнер, и применить его к моей структуре или коду, или как я могу указать контейнеру использовать режим моего кода, чтобы результаты всегда были идентичными?

РЕДАКТИРОВАТЬ: Спасибо за быстрый ответ ниже.

Вау, много места использовано. Дальнейшие результаты с другими контейнерами:

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 

РЕДАКТИРОВАТЬ 2:

В интересах тех, кто работает с пулами распределения:

Ура! Думаю, я достиг своей цели. Пример кода основан на реальном приложении, и, как и следовало ожидать, allocate и deallocate шаблона распределителя не просто вызывают new и delete. они передают в пул. До четверга пул представлял собой глобальный многомерный стиль фрагментирования (несколько разных плоскостей для общих запросов ожидаемого размера). allocate передаст требуемое количество байтов. Затем я шаблонизировал наш глобальный пул, но как-то неуклюже глобальный экземпляр нужно было отдельно инициализировать с нужным типом — вот тут-то и начались проблемы, это не тот тип! Я увидел возможность для allocate передавать только количество элементов вместо байтов. Как вы видели, это не сработало так, как я пытался. Моя ошибка заключалась в том, что вскоре после шаблонизации нашего пула я не понял, что могу просто поместить его статический экземпляр в свой класс шаблонов распределителя. Бум, проблема решена, теперь все размеры совпадают. Пул теперь работает нормально, теперь это шаблон, встроенный в класс шаблона распределителя, и он более компактен и эффективен, чем наша предыдущая версия. ~25 лет с C++, шаблоны не перестают меня удивлять. Спасибо за вашу помощь.


person MusicMaster    schedule 28.10.2017    source источник


Ответы (2)


multiset не хранит Item напрямую, а использует некоторую древовидную структуру, которая добавляет дополнительные указатели для навигации по дереву.

Он действительно использует TestAllocator<some_internal_node_type> для размещения объектов. И это размер типа узла, который вы получаете.

person Bo Persson    schedule 28.10.2017
comment
Я бы добавил, что именно для этого предназначен шаблон члена rebind — контейнер использует его для перехода от типа TestAllocator<Item> к типу TestAllocator<some_internal_node_type>, который он фактически использует. - person aschepler; 29.10.2017

Изменение функции отображения на:

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

Я получаю вывод:

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

который должен рассеять ваше замешательство. Тип шаблона, используемый мультимножеством, представляет собой класс узла, который сам содержит Item. Проверка заголовка мультинабора вашей реализации может помочь увидеть, когда распределители привязаны и используются.

person M.M    schedule 29.10.2017