2012-03-13 2 views
4

Я хотел бы создать шаблон класса, который принимает тип распределителя (как определено в стандартном разделе 17.6.3.5) в качестве аргумента шаблона. Я вижу, как std::allocator_traits<A> помогает заполнять любые отсутствующие элементы A с настройками по умолчанию. Помимо этого, есть ли что-нибудь в стандартной библиотеке или повысить, что поможет правильно использовать распределитель?Использование std :: allocator_traits <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)); 
    } 
    //... 
}; 

ответ

10

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

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

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

  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 нуждаются в нетривиальной инициализации, поэтому их срок службы начинается, когда их конструкторы завершаются, что делается путем размещения нового и вызова 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)); 

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

+1

Не означает, что любые другие типы, которые не должны использовать 'construct', противоречат предложению' storage_traits :: construct (m_alloc, &sp); ', или это не считается из-за' rebind'? Если я создаю 'хранилище 'и он все еще имеет член типа' T', тогда этот элемент создается вместе с 'storage', а не' construct'. Поэтому мне нужно было бы сделать какой-то «union» или 'static_cast' из' void' trick? – aschepler

+0

@aschepler, +1, хорошо замечен! исправлено сейчас, спасибо –

+0

Что касается вашего второго вопроса, да, вы можете использовать объединение или подходящий размер и подходящий массив 'char'. опыт проще просто гарантировать, что 'storage' содержит только тривиально-конструктивные данные, кроме' T', поэтому вы можете «построить» 'T' и просто присваивать значения другим частям (как правило, просто указателям и/или целым числам). Я отредактирую свой анвве r, чтобы отразить это. –