Последнее обновление:2026-10-12
Отказ от конструктора по умолчанию в C++¶
В C++ некоторые контейнеры при изменении размера методом resize() вызывают конструктор по умолчанию, что может быть вовсе не тем поведением, которое нам нужно. В этой статье рассказывается, как управлять этим процессом с помощью пользовательского аллокатора и избежать вызова конструктора по умолчанию.
Пример 1:
Знаете, что будет выведено в этом случае?
Ответ:
Здесь нужно понять несколько моментов:
Первое: почему после обратного resize данные превратились в нули, что произошло?
Второе: что делать, если я хочу сохранить исходные данные, то есть хочу получить такой вывод:
Третье: что произойдёт, если у элементов vector нет конструктора по умолчанию?
Четвёртое: какие ограничения есть у resize?
В этом разделе мы как раз займёмся этой мощной функциональностью — как заставить resize избежать вызова конструктора по умолчанию.
Примечание: из-за ограниченного объёма статьи весь код «ленивой» версии раздела обновлён в сообществе.
1. Разбор resize¶
Чтобы изложение было более плавным, я использую следующий пример: запретим конструирование Foo, а затем вызовем для него resize на vector.
В этом случае возникнет ошибка компиляции, и мы увидим целую кучу диагностических сообщений.
Первая проблема: в стеке вызовов видно, что мы дошли до __uninitialized_default_n, где проверяется, является ли текущий тип тривиальным, — это первое ограничение resize!
required from '_ForwardIterator std::__uninitialized_default_n(_ForwardIterator, _Size) [with _ForwardIterator = Foo*; _Size = long unsigned int]'
710 | return __uninitialized_default_n_1<__is_trivial(_ValueType)
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
711 | && __can_fill>::
Вторая проблема: поскольку мы запретили конструктор по умолчанию, аллокатору не удалось создать на исходном адресе через placement new объект по умолчанию, и возникла ошибка — это второе ограничение!
gcc/14.1.0_1/include/c++/14/bits/stl_construct.h:119:7: error: use of deleted function 'Foo::Foo()'
119 | ::new((void*)__p) _Tp(std::forward<_Args>(__args)...);
| ^~~~~~~~~~~~~~~~~~~
Раз мы уже дошли до этого, можно переходить к сути: видно, что при resize для выделения памяти используется аллокатор. Когда переданный размер больше size (предположим, что мы остаёмся в пределах capacity, то есть адрес валиден), аллокатор с помощью placement new создаёт объект, сконструированный конструктором по умолчанию.
Исходники реализации gcc можно показать здесь — просто, правда? Это всего лишь вызов construct.
__try
{
typedef __gnu_cxx::__alloc_traits<_Allocator> __traits;
for (; __n > 0; --__n, (void)++__cur)
__traits::construct(__alloc, std::__addressof(*__cur));
return __cur;
}
__catch(...)
{
std::_Destroy(__first, __cur, __alloc);
__throw_exception_again;
}
Итак, с принципом работы resize мы разобрались, вернёмся к вопросу из самого начала.
2. Как избежать вызова конструктора по умолчанию¶
Как же избежать вызова конструктора по умолчанию?
На самом деле всё просто: нужно отключить construct, используемый gcc по умолчанию. А как его отключить?
Здесь и пригодится второй параметр vector — аллокатор!
3. Аллокатор¶
Для решения этой проблемы можно использовать пользовательский аллокатор. Пользовательский аллокатор позволяет управлять поведением конструирования объектов и выбирать, использовать ли конструктор по умолчанию при вызове resize. Вот простая реализация пользовательского аллокатора — приведён только ключевой код.
template <class T>
class no_init_alloc {
public:
using value_type = T;
// Override construct method to do nothing
template <class U>
void construct(U* p) {
// Do nothing, memory is not initialized
}
};
Нам достаточно, чтобы construct ничего не делал, — и тогда им можно пользоваться, то есть:
В результате получим:
а не:
Ну что, разобрались?
Конец раздела
Похожие темы¶
- Идиома списков инициализации класса в C++ | C++那些事
- C++: классы-шаблоны вместо шаблонов функций | C++那些事
- Идиома copy-and-swap в C++ | C++那些事
- Внедрение зависимостей в C++ | C++那些事
- Сильнотипизированные перечисления enum class в C++ | C++那些事
- Header-Only в C++ против разделения реализации | C++那些事
- Идиома ленивых вычислений в C++ | C++那些事
- Идиома таблиц поиска в C++ | C++那些事
- Указатели на члены и информация о типах в C++ | C++那些事
- Идиома Mixin в C++ | C++那些事