Я не знаю ничего, что могло бы облегчить жизнь, 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.
Нет, обертки или ярлыков нет,требования распределителя C ++ 11 делают работу автора контейнера намного более сложной.Требования немного отличаются для каждого контейнера, в зависимости от того, как он управляет памятью.Для вектороподобного типа в операторе копирования-копирования, если propagate_on_container_copy_assignment
(POCCA) имеет значение false, а существующая емкость больше размера исходного объекта, вы можете повторно использовать существующую память (если POCCA имеет значение true и новыйВыделитель не равен, вы не можете повторно использовать старую память, так как невозможно будет перераспределить ее позже после замены распределителя), но эта оптимизация мало помогает для контейнера на основе узла, такого как списокили карта.
Это выглядит почти правильно, хотя вы, вероятно, хотите заменить
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));
Что немного облегчает жизнь.