Контейнеры
Выбор STL-контейнеров, сложности, итераторы и инвалидация.
31 вопросов
JuniorТеорияОчень частоКак выбирать между vector, list, map, set и unordered_map?
Как выбирать между vector, list, map, set и unordered_map?
По умолчанию vector — cache-friendly обход. map/set — для сортированного O(log n), unordered_map/set — для среднего O(1) без порядка, list — только когда стабильные итераторы или O(1) splice важнее локальности кэша.
Типичные ошибки
- ✗Выбирать list из-за O(1) вставки, игнорируя поиск и кэш
- ✗Ожидать отсортированный обход unordered_map
- ✗Игнорировать худший случай хеш-таблицы
Уточняющие вопросы
- →Какой контейнер выбрать для LRU-cache?
- →Почему vector часто быстрее list?
JuniorТеорияОчень частоКогда выбирать std::array вместо std::vector и наоборот?
Когда выбирать std::array вместо std::vector и наоборот?
std::array<T, N> — для известных на этапе компиляции размеров: стековая аллокация, обёртка над C-массивом без накладных расходов. std::vector<T> — для динамических размеров или больших буферов; владеет heap-блоком с ростом по capacity.
Типичные ошибки
- ✗Класть огромный
std::array<T, 1'000'000>на стек — overflow - ✗Использовать
std::vectorдля координаты из 3 элементов — лишняя heap-аллокация - ✗Забывать, что
std::array<int, 0>валиден иempty()возвращает true
Уточняющие вопросы
- →Как работает CTAD для
std::array(вывод из braced-списка)? - →Может ли
std::arrayбытьconstexpr?
MiddleДебаггингОчень частоПочему удаление в этом цикле — неопределённое поведение?
Почему удаление в этом цикле — неопределённое поведение?
vector::erase инвалидирует it и все итераторы после него; следующий ++it продвигает уже висячий итератор — UB. Решение: использовать возвращаемое значение, it = v.erase(it);, и делать ++it только если ничего не удалили. Либо std::erase_if(v, pred); (C++20).
Типичные ошибки
- ✗Считать, что erase оставляет итератор на следующем элементе
- ✗Винить повторные вызовы end() вместо инвалидации итераторов
- ✗Думать, что идиома erase-remove здесь не нужна
Уточняющие вопросы
- →Как идиома erase-remove полностью устраняет эту проблему?
- →Какие контейнеры НЕ инвалидируют другие итераторы при erase?
MiddleТеорияОчень частоЧто такое инвалидация итераторов и чем она отличается у разных контейнеров?
Что такое инвалидация итераторов и чем она отличается у разных контейнеров?
Инвалидация — итератор больше не указывает на валидный элемент после мутации. Реаллокация vector инвалидирует всё; вставка в list/map сохраняет итераторы; rehash unordered_map инвалидирует.
Типичные ошибки
- ✗Хранить итераторы vector после push_back без reserve
- ✗Удалять элементы в цикле, не используя итератор, возвращённый erase
- ✗Думать, что ссылки всегда инвалидируются вместе с итераторами
Уточняющие вопросы
- →Покажите корректный паттерн удаления во время обхода.
- →Как reserve влияет на инвалидацию vector?
MiddleДебаггингОчень частоПочему *p повисает после push_back?
Почему *p повисает после push_back?
push_back может перевыделить память вектора при росте за пределы вместимости, инвалидируя p, который теперь висячий — UB. Указатели, ссылки и итераторы внутрь vector инвалидируются перевыделением. Решение: заново получить p после вставки или reserve вместимости заранее.
Типичные ошибки
- ✗Считать, что push_back никогда не перемещает существующие элементы
- ✗Думать, что сырые указатели переживают перевыделение вектора, а итераторы — нет
- ✗Забывать, что рост вместимости перемещает весь буфер
Уточняющие вопросы
- →Когда именно
vector::push_backинвалидирует ссылки и итераторы? - →Как
reserveделает сериюpush_backустойчивой к инвалидации указателей?
JuniorТеорияЧастоЧем reserve отличается от resize для std::vector?
Чем reserve отличается от resize для std::vector?
reserve(n) увеличивает capacity() минимум до n без конструирования элементов — полезно перед известной серией push_back. resize(n) меняет size() до n, value-инициализируя новые элементы и уничтожая лишние.
Типичные ошибки
- ✗
v.reserve(n), затемv[i] = xвместоpush_back— UB, доступ за size - ✗Вызывать
resizeкогда нужна только capacity — оплатишь value-initialisation всех элементов - ✗Звать
reserve(0)ожидая сжатия — используйтеshrink_to_fit(и тот не обязывает)
Уточняющие вопросы
- →Каков типичный коэффициент роста
std::vectorи почему? - →Что вернёт
capacity()сразу послеclear()?
JuniorТеорияЧастоКак устроены std::stack и std::queue и какой у них underlying-контейнер?
Как устроены std::stack и std::queue и какой у них underlying-контейнер?
Оба — адаптеры, оборачивающие underlying-контейнер (по умолчанию std::deque) с ограниченным интерфейсом. std::stack даёт LIFO push/pop/top; std::queue — FIFO push/pop/front/back.
Типичные ошибки
- ✗Пытаться итерировать
std::stack— у адаптеров итераторов нет - ✗
std::queue<T, std::vector>не компилируется — у vector нетpop_front - ✗Забывать, что
pop()возвращает void; сначалаtop()/front(), потомpop()
Уточняющие вопросы
- →Почему
pop()не возвращает значение? (exception safety) - →Когда брать
std::stack<T, std::vector<T>>вместо default?
MiddleТеорияЧастоКак устроен std::deque и каковы правила инвалидации итераторов?
Как устроен std::deque и каковы правила инвалидации итераторов?
std::deque — страничная таблица указателей на блоки фиксированного размера: O(1) push/pop с обоих концов, произвольный доступ, не contiguous. Push/pop с конца инвалидирует итераторы, но сохраняет ссылки; вставка в середину — всё.
Типичные ошибки
- ✗Передавать
&deque[0]в C-API, ждущий contiguous-буфер — сломается - ✗Считать, что push_back не инвалидирует итераторы (инвалидирует, в отличие от
std::list) - ✗Выбирать
dequeвместоvectorдля очереди без измерений —vector+ индекс часто быстрее на cache-friendly нагрузках
Уточняющие вопросы
- →Почему
std::queueпо умолчанию обёртка вокругstd::deque? - →Сравните deque с кольцевым буфером для FIFO.
MiddleПроизводительностьЧастоКогда emplace_back экономит работу по сравнению с push_back и когда они эквивалентны?
Когда emplace_back экономит работу по сравнению с push_back и когда они эквивалентны?
emplace_back(args...) строит элемент на месте из аргументов конструктора, экономя временный объект, который создал бы push_back(T(a,b)). С готовым T push_back(std::move(x)) эквивалентен и яснее.
Типичные ошибки
- ✗Использовать
emplace_back(existing)там, гдеpush_back(existing)идентичен и понятнее - ✗Вызывать
emplace_backс неявными преобразованиями, которыеpush_backзапретил бы из-заexplicit - ✗Ждать, что
emplace_backпропустит реаллокацию — она всё равно возможна
Уточняющие вопросы
- →Чем
try_emplaceотличается отemplaceдля map? - →Почему
emplace_backс C++17 возвращает ссылку, а не void?
MiddleТеорияЧастоКак проверить, что контейнер пуст? Почему size() == 0 — плохая практика?
Как проверить, что контейнер пуст? Почему size() == 0 — плохая практика?
Используйте container.empty() — он O(1) для всех стандартных контейнеров (включая std::list до C++11, где size() был O(n)) и прямо выражает намерение.
Типичные ошибки
- ✗Использовать
size() == 0— работает корректно, но является code smell;empty()предпочтительнее - ✗Не предоставлять
empty()в пользовательском контейнере — должно быть минимумreturn size() == 0; - ✗Вызывать
empty()на строке и рассчитывать, что оно проверяет наличие пробелов —empty()только проверяет нулевую длину, а не содержимое
Уточняющие вопросы
- →Что добавляет
std::empty(container)(свободная функция, C++17) по сравнению сcontainer.empty()? - →Может ли
empty()вернутьtrueдля контейнера с зарезервированной ёмкостью?
MiddleКодЧастоКак написать кастомный хеш для пользовательского ключа в unordered_map?
Как написать кастомный хеш для пользовательского ключа в unordered_map?
Специализируйте std::hash<MyKey> в namespace std или передайте хеш-callable вторым шаблонным параметром. Комбинируйте поля через hash_combine (не тривиальный XOR) и предоставьте согласованный operator==.
Типичные ошибки
- ✗Хешировать только одно поле структуры — высокий процент коллизий
- ✗Забывать согласованность
operator==и хеша (равные ключи — равный хеш) - ✗Специализировать
std::hashв другом namespace — её просто не найдут
Уточняющие вопросы
- →На чём обычно основан
std::hash<std::string>(siphash, fnv и т.д.)? - →Как сделать transparent-хеш для heterogeneous lookup с
string_view?
MiddleТеорияЧастоКак устроен std::list внутри?
Как устроен std::list внутри?
std::list<T> — двусвязный список из узлов на куче (значение + prev + next) с фиктивной головой/хвостом. O(1) вставка/удаление везде, плохая локальность кэша, O(1) size() с C++11.
Типичные ошибки
- ✗Использовать
std::listдля последовательного доступа, гдеstd::vectorбудет значительно быстрее благодаря кэш-линиям - ✗Итерировать список с арифметикой индексов — у
std::listнетoperator[]; используйте итераторы - ✗Склеивать из одного списка в другой и затем проверять размер исходного — splice переносит узлы, но размер нужно обновить (O(n) в C++11 для полного splice)
Уточняющие вопросы
- →Что такое
std::forward_listи чем он жертвует ради меньших накладных расходов памяти? - →Как работает
std::list::sort, учитывая, что он не может использоватьstd::sort?
MiddleТеорияЧастоКогда использовать map vs unordered_map? Сравнение сложности.
Когда использовать map vs unordered_map? Сравнение сложности.
std::map — красно-чёрное дерево: O(log n), сортированная итерация, нужен только operator<. std::unordered_map — хэш-таблица: O(1) средний (O(n) худший), произвольный порядок, нужны operator== и хэш.
Типичные ошибки
- ✗Использовать
unordered_mapс пользовательским ключом без специализации хэша — ошибка компиляции или использование умолчания, которого может не быть - ✗Рассчитывать на упорядочивание
unordered_map— оно неопределено и может меняться после рехэширования - ✗Игнорировать O(n) в худшем случае для
unordered_map— может быть вызвано hash-DoS атаками, если ключи приходят из ненадёжного источника
Уточняющие вопросы
- →Как написать пользовательский хэш для структуры с несколькими полями?
- →Что такое коэффициент загрузки в
unordered_mapи какmax_load_factorвлияет на производительность?
MiddleТеорияЧастоКак std::priority_queue упорядочивает элементы и как сделать min-heap?
Как std::priority_queue упорядочивает элементы и как сделать min-heap?
По умолчанию max-heap через std::less<T> поверх vector<T>; для min-heap передайте std::greater<T> как компаратор. Push/pop — O(log n) через push_heap/pop_heap; top — O(1).
Типичные ошибки
- ✗Делать min-heap отрицанием значений — ломается для unsigned и при переполнении
- ✗Итерация по
priority_queue— итераторов нет; чтобы прочитать, надо pop - ✗Кастомный компаратор со состоянием — должен быть strict weak ordering, без состояния
Уточняющие вопросы
- →Как работают
std::make_heap,push_heap,pop_heapповерх вектора? - →Как реализовать decrease-key (в priority_queue его нет)?
MiddleТеорияЧастоЧто такое идиома erase-remove?
Что такое идиома erase-remove?
std::remove лишь сдвигает не совпадающие элементы вперёд и возвращает новый логический конец; хвост не определён. Вызовите container.erase(new_end, end()), чтобы реально отбросить хвост — remove сам не может, он работает на любых диапазонах.
Типичные ошибки
- ✗Вызывать
std::removeбез вызоваerase— контейнер сохраняет исходный размер с мусорными значениями в конце - ✗Применять erase-remove к
std::list— уlistесть методremove/remove_ifO(n) без перемещения элементов, предпочтительнее его - ✗Вызывать erase-remove внутри range-for цикла — инвалидирует итераторы, UB
Уточняющие вопросы
- →Как
std::erase_if(C++20) работает для ассоциативных контейнеров типаstd::map? - →Почему
std::removeоставляет неопределённые значения в хвосте вместо их обнуления?
MiddleТеорияЧастоЧто такое std::string_view и каковы его подводные камни по времени жизни?
Что такое std::string_view и каковы его подводные камни по времени жизни?
std::string_view (C++17) — невладеющий view (указатель+длина) над буфером символов. Не продлевает время жизни: view от временного std::string сразу dangling, поэтому не храните его дольше источника.
Типичные ошибки
- ✗Возвращать
string_viewиз функции, локально строящейstd::string— dangling - ✗Передавать
string_viewв C-API, ждущие null-terminated — view не обязан быть null-terminated - ✗Создавать
string_viewот rvaluestd::string— view умирает вместе с временным
Уточняющие вопросы
- →Когда
const std::string&предпочтительнееstd::string_viewкак параметр? - →Чем
string_view::data()отличается отc_str()(его нет)?
MiddleТеорияЧастоКак std::unordered_map обрабатывает коллизии хеша и зачем нужен bucket-интерфейс?
Как std::unordered_map обрабатывает коллизии хеша и зачем нужен bucket-интерфейс?
unordered_map использует separate chaining — каждый bucket это связный список коллидирующих узлов. Bucket-API (load_factor, max_load_factor, rehash) задаёт момент rehash; rehash инвалидирует все итераторы.
Типичные ошибки
- ✗Считать, что
unordered_map— open addressing; стандарт требует separate chaining (insert не инвалидирует ссылки, если нет rehash) - ✗Использовать плохой хеш (например,
hash<int>— identity) — все ключи в одном бакете - ✗Не вызывать
reserve(n)перед массовой вставкой — много инкрементальных rehash
Уточняющие вопросы
- →Почему
std::unordered_mapможет быть медленнееstd::mapна малых размерах? - →Как написать кастомные
HashиKeyEqual?
MiddleПроизводительностьЧастоКак оптимизировать удаление элемента из середины вектора?
Как оптимизировать удаление элемента из середины вектора?
Стандартный erase(it) — O(n), сдвигает последующие элементы влево. Если порядок не важен, используйте swap-and-pop: меняем местами с последним и вызываем pop_back() — O(1).
Типичные ошибки
- ✗Использовать трюк swap-and-pop, когда порядок важен — он меняет относительное положение оставшихся элементов
- ✗Вызывать
eraseв цикле с прямо движущимся индексом — индекс нужно корректировать после каждого удаления или аккуратно использовать итераторы - ✗Не рассматривать
std::stable_partition, когда нужно удалить много элементов сразу, сохраняя порядок
Уточняющие вопросы
- →Какова гарантия инвалидации итераторов для
std::vector::erase? - →Бенчмарк: при каком размере элемента и количестве
std::listстановится быстрееstd::vectorпри многократных удалениях в середине?
MiddleТеорияЧастоКак устроен std::vector внутри?
Как устроен std::vector внутри?
Три указателя (begin, end, end_of_storage) поверх contiguous heap-блока. При переполнении выделяется блок 2x, элементы перемещаются (если noexcept) или копируются, старый освобождается — амортизируя push_back до O(1).
Типичные ошибки
- ✗Считать, что
size() == capacity()— size это количество элементов, capacity это выделенное хранилище - ✗Не вызывать
reserveперед вставкой известного количества элементов — вызывает множество перевыделений - ✗Хранить итераторы или указатели в вектор и затем делать push_back — перевыделение инвалидирует все итераторы
Уточняющие вопросы
- →Почему
std::vector<bool>имеет специальную реализацию и какие у неё подводные камни? - →В чём разница между
shrink_to_fitиclear?
JuniorТеорияИногдаНазовите категории итераторов и какие контейнеры дают каждую.
Назовите категории итераторов и какие контейнеры дают каждую.
Шесть категорий: input/output (single-pass), forward (forward_list), bidirectional (list, set, map), random-access (vector, deque, array) и contiguous из C++20 (vector, array, string, span).
Типичные ошибки
- ✗Вызывать
std::sortдляstd::list— ошибка компиляции; list-итераторы не random-access; используйтеlist::sort - ✗Вычитать итераторы
forward_listожидая O(1) — они не random-access - ✗Считать contiguous и random-access одним и тем же —
dequerandom-access, но не contiguous
Уточняющие вопросы
- →Что добавляет C++20
std::contiguous_iteratorсверхstd::random_access_iterator? - →Как концепты итераторов C++20 заменяют iterator_traits?
JuniorТеорияИногдаКак посчитать элементы в std::list? Почему до C++11 это было O(n)?
Как посчитать элементы в std::list? Почему до C++11 это было O(n)?
C++11 требует O(1) size() через кэшированный счётчик; до C++11 было определено реализацией и часто O(n). Цена — splice всего списка стал O(n), так как счётчик нужно обновлять.
Типичные ошибки
- ✗Считать
list::size()O(n) в современном коде и использовать ручной счётчик — ненужно с C++11 - ✗Забывать, что компромисс O(1)/O(n) для splice был осознанным решением дизайна, а не упущением
- ✗Использовать
std::distance(list.begin(), list.end())для размера — всегда O(n) для двунаправленных итераторов
Уточняющие вопросы
- →Какова временная сложность
std::list::spliceв C++11 и почему? - →Когда следует предпочитать
std::listвместоstd::vector, несмотря на лучшую кэш-производительностьvector?
JuniorТеорияИногдаЧем set отличается от multiset и когда что использовать?
Чем set отличается от multiset и когда что использовать?
std::set хранит уникальные ключи; insert возвращает pair<iter, bool> с false при дубликате. std::multiset допускает дубликаты и всегда успешен; используйте его, когда дубликаты важны, и equal_range для обхода совпадений.
Типичные ошибки
- ✗
multiset::erase(key)удаляет все дубликаты — это часто неожиданно - ✗Ходить по равным ключам через
find(k)и++it— работает, ноequal_rangeпонятнее - ✗Хранить кастомные объекты без strict weak ordering — UB
Уточняющие вопросы
- →Чем
set::erase(iter)отличается отset::erase(key)по возвращаемому значению? - →Какова сложность
multiset::count(k)и почему?
MiddleТеорияИногдаКак расширять контейнеры STL с пользовательскими аллокаторами или политиками?
Как расширять контейнеры STL с пользовательскими аллокаторами или политиками?
Передайте кастомный аллокатор как шаблонный параметр контейнера — он должен предоставлять value_type, allocate, deallocate (или умолчания allocator_traits). C++17 std::pmr даёт полиморфные ресурсы памяти без перешаблонизации.
Типичные ошибки
- ✗Забывать механизм
rebind— аллокаторы для контейнеров на основе узлов могут внутренне переориентироваться на другой тип - ✗Не делать аллокатор stateless при использовании с
std::vector— stateful аллокаторы влияют на семантику копирования/перемещения - ✗Реализовывать
allocateбез проверкиn == 0— неопределённое поведение в некоторых требованиях аллокатора
Уточняющие вопросы
- →В чём разница между
std::pmr::monotonic_buffer_resourceиstd::pmr::pool_options? - →Как использовать
std::pmr::vectorс буфером на стеке для временных выделений?
MiddleТеорияИногдаЧто должен реализовывать класс, чтобы быть корректным итератором C++?
Что должен реализовывать класс, чтобы быть корректным итератором C++?
ForwardIterator должен предоставлять operator*, prefix operator++, operator==/!=, конструктор копирования и псевдонимы (iterator_category, value_type, difference_type, pointer, reference) через вложенные типы или iterator_traits.
Типичные ошибки
- ✗Забывать предоставить
iterator_category— алгоритмы молча используют наиболее консервативное поведение - ✗Не реализовывать постфиксный
operator++— некоторые алгоритмы и range-for его требуют - ✗Не удовлетворять требованию equality comparable —
operator==должен быть согласован сoperator!=
Уточняющие вопросы
- →Как
std::sentinel_forиstd::sized_sentinel_forC++20 улучшают завершение диапазона по сравнению со совпадающими типами begin/end? - →В чём разница между
iteratorиconst_iteratorи как предоставить оба?
MiddleТеорияИногдаЧто гарантирует shrink_to_fit и когда его использовать?
Что гарантирует shrink_to_fit и когда его использовать?
shrink_to_fit() — необязывающий запрос освободить лишнюю capacity; реализация может его проигнорировать. Переносимый приём — swap-идиома: std::vector<T>(v).swap(v).
Типичные ошибки
- ✗Звать
shrink_to_fitпосле каждой операции — борьба с амортизированным ростом - ✗Ждать жёсткой гарантии — реализация может проигнорировать
- ✗Делать на крошечных контейнерах, где копия стоит дороже сэкономленных байт
Уточняющие вопросы
- →Зачем swap-идиома помимо
shrink_to_fit? - →Существует ли
std::deque::shrink_to_fitи что он делает?
MiddleТеорияИногдаЧто такое std::span и когда заменять им T* + size_t?
Что такое std::span и когда заменять им T* + size_t?
std::span<T> (C++20) — невладеющий view (указатель+длина) над contiguous-диапазоном. Используйте как параметр функции, чтобы единообразно принимать vector, array или C-массивы без шаблонов; буфер должен жить дольше span.
Типичные ошибки
- ✗Возвращать
spanна локальныйvector— dangling - ✗Хранить
spanв долгоживущем объекте, источник которого может пере-аллоцироваться — view инвалидируется - ✗Путать
span<T>иspan<const T>— первый позволяет изменения через view
Уточняющие вопросы
- →Чем
std::spanотличается отgsl::spanиз GSL? - →Зачем span с фиксированным extent (
std::span<T, N>)?
MiddleТеорияИногдаЧем std::string отличается от std::vector<char> по реализации и поведению?
Чем std::string отличается от std::vector<char> по реализации и поведению?
Оба contiguous, но std::string добавляет гарантированный null-терминатор (c_str() — O(1)), small-string optimisation, текстовые API (find, substr) и char_traits. У vector<char> ничего этого нет — для текста используйте string.
Типичные ошибки
- ✗Хранить текст в
vector<char>и потом вручную добавлять null-терминатор для C-API - ✗Считать
string::data()null-terminated до C++11 — стандарт это не гарантировал - ✗Хранить бинарные блобы в
string— работает, ноvector<std::byte>яснее по смыслу
Уточняющие вопросы
- →Что такое
char_traitsи как его используетstd::wstring? - →Почему
std::basic_string— шаблон класса?
SeniorТеорияИногдаКакова роль аллокатора в STL-контейнерах и когда писать свой?
Какова роль аллокатора в STL-контейнерах и когда писать свой?
Шаблонный параметр Allocator управляет источником памяти контейнера через allocate/deallocate. Кастомные нужны для arena/pool/NUMA/трекинга; C++17 std::pmr::polymorphic_allocator подменяет поведение во время выполнения.
Типичные ошибки
- ✗Забывать, что контейнеры с разными аллокаторами — разные типы и по умолчанию не сравниваются/swap
- ✗Писать stateful-аллокатор и неправильно обрабатывать propagation traits
- ✗Сравнивать производительность без workload — pool-аллокатор выигрывает на множестве маленьких, проигрывает на больших
Уточняющие вопросы
- →Что такое
propagate_on_container_copy_assignmentи когда это важно? - →Как
std::pmr::vectorсовместим сstd::vector?
SeniorТеорияИногдаВнутреннее устройство std::set, std::map, std::unordered_map и std::hash.
Внутреннее устройство std::set, std::map, std::unordered_map и std::hash.
std::set/std::map — красно-чёрные деревья: O(log n) операции, стабильные итераторы при вставке. std::unordered_map — хэш-таблица с раздельным связыванием: O(1) средний, O(n) худший при плохом std::hash.
Типичные ошибки
- ✗Реализовывать
std::hashчерез XOR всех полей — создаёт много коллизий для ключей, отличающихся незначительно - ✗Изменять ключ внутри
std::setилиstd::mapчерезconst_cast— молча нарушает инвариант BST - ✗Считать, что указатели на элементы
unordered_mapстабильны после рехэширования — ссылки/указатели на значения инвалидируются
Уточняющие вопросы
- →В чём разница между
std::set::insertиstd::set::emplaceс точки зрения производительности? - →Чем
std::unordered_map::reserveотличается отrehash?
SeniorПроизводительностьРедкоЧто такое std::flat_map (C++23) и когда он лучше std::map?
Что такое std::flat_map (C++23) и когда он лучше std::map?
std::flat_map<K, V> (C++23) — sorted contiguous container на двух параллельных vector: O(log n) бинарный поиск с локальностью кэша лучше std::map, но O(n) insert/erase. Лучшее для read-heavy нагрузок.
Типичные ошибки
- ✗Применять
flat_mapк write-heavy нагрузке — O(n) insert убивает производительность - ✗Ждать стабильность итераторов как у
std::map— у flat_map её нет - ✗Забывать, что range-конструкторы либо сортируют, либо принимают предсортированный вход через тег
sorted_unique_t
Уточняющие вопросы
- →Как
flat_mapработает с аллокаторами? - →Что такое
flat_setи когда заменяетset?
SeniorПроизводительностьРедкоЧто такое small-buffer optimisation в контейнерах и зачем нужен boost::small_vector?
Что такое small-buffer optimisation в контейнерах и зачем нужен boost::small_vector?
SBO встраивает inline-буфер на N элементов; пока size() ≤ N, heap-аллокаций нет, выше — динамическое выделение. Цена: избегаем heap для малых размеров, но sizeof(container) растёт на N * sizeof(T).
Типичные ошибки
- ✗Брать слишком большой N — тратит стек и раздувает объекты, передаваемые по значению
- ✗Сохранять
small_vector::data()через операции — указатель меняется при переходе между inline и heap - ✗Считать, что у
std::vectorесть SBO — стандарт его не даёт
Уточняющие вопросы
- →Как SSO у
std::stringвзаимодействует с move-семантикой? - →Почему у
std::vectorнет SBO в стандарте?