Как исправить код из книги Lippman C++ Primer использующий std::allocator?
На странице 666 в последнем русском издании показан исходный код реализации простого вектора строк. В этом коде используется std::allocator а также его методы, в частности construct, который как я понял убрали с 20 версии языка. Получается что предоставленный код неактуальный на данный момент. Как его можно изменить чтобы все таки изучить тему "Классы управляющие динамической памятью"?
Вот версия исходного кода предоставленного в книге:
class StrVec
{
public:
StrVec() : elements(nullptr), first_free(nullptr), cap(nullptr) {}
StrVec(const StrVec&);
StrVec& operator=(const StrVec&);
~StrVec();
void push_back(const std::string&);
std::size_t size() const { return first_free - elements; }
std::size_t capacity() const { return cap - elements; }
std::string* begin() const { return elements; }
std::string* end() const { return first_free; }
private:
std::allocator<std::string> alloc;
void chk_n_alloc()
{
if (size() == capacity()) reallocate();
};
std::pair<std::string*, std::string*> alloc_n_copy(const std::string*, const std::string*);
void free();
void reallocate();
std::string* elements;
std::string* first_free;
std::string* cap;
};
void StrVec::push_back(const std::string& str)
{
chk_n_alloc();
alloc.construct(first_free++, str); // ??????
}
Ответы (2 шт):
Используйте allocator_traits:
::std::allocator_traits<::std::allocator<std::string>>::construct(alloc, first_free++, str);
Есть несколько вариантов:
- Выбросить аллокатор и руками звать
operator new(n)для выделения памяти,operator delete(ptr)для освобождения,std::construct_at(ptr, params)(или::new((void *)ptr) T(params)) для конструирования, иstd::destroy_at(ptr)(илиptr->T::~T()) для разрушения.
Использовать аллокатор имеет смысл только если класс шаблонный, и аллокатор можно заменить через аргумент шаблона.
Все-таки использовать аллокатор.
Руками никакие методы (и типы из) аллокатора не звать, и для всего использовать
std::allocator_traits(желательно в т.ч. дляallocateиdeallocate). Если бы автор книги так сделал, его код бы не сломался.Желательно навесить на поле с аллокатором
[[no_unique_address]], чтобы пустой аллокатор не занимал места.Дальше, если вы хотите поддерживать произвольные аллокаторы (сделать его шаблонным параметром), то без поллитра не разберешься. Очень аккуратно читаем требования к AllocatorAwareContainer и Allocator. Первое - это то, чему должен соответствовать ваш класс, а второе - чтобы понимать, как пользоваться
allocator_traits.В том числе:
std::size_tи сырые указатели долой, используйтеstd::allocator_traits<T>::size_type,std::allocator_traits<T>::pointerи прочие.Копирующий конструктор должен звать
std::allocator_traits<T>::select_on_container_copy_construction().Перемещающий конструктор должен делать
std::moveна аллокаторе.Копирующее присваивание должно делать
if constexpr (std::allocator_traits<T>::propagate_on_container_copy_assignment::value)перед тем, как присваивать аллокатор.Перемещающее присваивание должно делать
if constexpr (std::allocator_traits<T>::propagate_on_container_move_assignment::value)перед тем, как присваивать аллокатор.swap()должен проверятьif constexpr (std::allocator_traits<T>::propagate_on_container_swap::value)перед тем, как свапать аллокатор.Операторы присваивания дополнительно должны делать хитрую проверку, если они не стали присваивать аллокатор (см. выше).
if constexpr (std::allocator_traits<T>::is_always_equal::value) // A else if (alloc_a == alloc_b) // A else // BГде
A- обычное поведение, аBозначает, что аллкаторы "разные", и не могут освобождать память, выделенную друг другом. В случаеB, старую память в целевом объекте нужно выбросить, и выделить заново новым аллокатором.