Использование std :: allocator_traits <A> - PullRequest
7 голосов
/ 13 марта 2012

Я хотел бы разработать шаблон класса, который принимает тип распределителя (как определено в разделе 17.6.3.5 стандарта) в качестве аргумента шаблона. Я вижу, как std::allocator_traits<A> старательно заполняет все недостающие элементы A настройками по умолчанию. Кроме того, есть ли в Стандартной библиотеке что-либо, что помогло бы правильно использовать распределитель?

В частности:

  1. Чтобы соблюдать определения типа, такие как std::allocator_traits<A>::propagate_on_container_copy_assignment, нужно ли проверять эти вещи в специальных функциях-членах каждого класса, который имеет член типа A? Или есть какой-нибудь тип оболочки, который я мог бы использовать вместо члена, который бы позаботился об этом?

  2. Если я хочу перераспределить, чтобы уменьшить количество выделений путем хранения дополнительных данных рядом с видимыми пользователем объектами, уместно ли перепривязывать распределитель как-то так?

.

template<typename T, typename A>
class MyClass
{
private:
    //...
    struct storage {
        int m_special_data;
        T m_obj;
    };
    typedef typename std::allocator_traits<A>::template rebind_alloc<storage>
        storage_alloc;
    typedef typename std::allocator_traits<A>::template rebind_traits<storage>
        storage_traits;
    storage_alloc m_alloc;

    static T* alloc(T&& obj)
    {
        storage_traits::pointer sp = storage_traits::allocate(m_alloc, 1);
        sp->m_special_data = 69105;
        return ::new(&sp->m_obj) T(std::move(obj));
    }
    //...
};

1 Ответ

12 голосов
/ 16 июня 2012

Я не знаю ничего, что могло бы облегчить жизнь, allocator_traits на самом деле упрощает написание распределителя, предоставляя весь стандартный код, но это не помогает использовать распределитель.

Чтобы я мог использовать один API распределителя в коде C ++ 03 и C ++ 11, который я добавил <ext/alloc_traits.h> в GCC 4.7, шаблон класса __gnu_cxx::__alloc_traits предоставляетнепротиворечивый API, который использует allocator_traits в режиме C ++ 11 и вызывает соответствующие функции-члены непосредственно в распределителе в режиме C ++ 03.

  1. Нет, обертки или ярлыков нет,требования распределителя C ++ 11 делают работу автора контейнера намного более сложной.Требования немного отличаются для каждого контейнера, в зависимости от того, как он управляет памятью.Для вектороподобного типа в операторе копирования-копирования, если propagate_on_container_copy_assignment (POCCA) имеет значение false, а существующая емкость больше размера исходного объекта, вы можете повторно использовать существующую память (если POCCA имеет значение true и новыйВыделитель не равен, вы не можете повторно использовать старую память, так как невозможно будет перераспределить ее позже после замены распределителя), но эта оптимизация мало помогает для контейнера на основе узла, такого как списокили карта.

  2. Это выглядит почти правильно, хотя вы, вероятно, хотите заменить

    return ::new(&sp->m_obj) T(std::move(obj));
    

    на

    A a(m_alloc);    
    std::allocator_traits<A>::construct(a, &sp->m_obj, std::move(obj));
    return &sp->m_obj;
    

Как указано в [container.requirements.general] / 3, контейнеры, использующие распределитель, используют allocator_traits<A>::construct для создания самого типа элемента T, но любые другие выделенные типы (например, storage) не должны использовать construct.

Если создается storage, то он будет создавать storage::m_obj, если только этот элемент не является типом, который можно оставить неинициализированным, например, std::aligned_storage<sizeof(T)>, который позже можно будет явно инициализировать с помощью allocator_traits<A>::construct,В качестве альтернативы, индивидуально создайте каждый элемент, который нуждается в нетривиальном построении, например, если storage также имел элемент string:

    storage_traits::pointer sp = storage_traits::allocate(m_alloc, 1);
    sp->m_special_data = 69105;
    ::new (&sp->m_str) std::string("foobar");
    A a(m_alloc);    
    std::allocator_traits<A>::construct(a, &sp->m_obj, std::move(obj));
    return &sp->m_obj;

Элемент m_special_data является тривиальным типом, поэтому его время жизни начинается, как толькодля него выделено хранилище.Члены m_str и m_obj нуждаются в нетривиальной инициализации, поэтому их время жизни начинается после завершения их конструкторов, что делается с помощью размещения new и вызова construct соответственно.

Edit : я недавно узнал, что в стандарте есть дефект (о котором я сообщил), и вызовам construct не нужно использовать распределитель отскока, поэтому эти строки:

    A a(m_alloc);    
    std::allocator_traits<A>::construct(a, &sp->m_obj, std::move(obj));

можно заменить на:

    std::allocator_traits<storage_alloc>::construct(m_alloc, &sp->m_obj, std::move(obj));

Что немного облегчает жизнь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...