Храните отдельно буфер, size и capacity. emplace_back конструирует объект в сырой памяти, push_back делегирует ему копирование, pop_back разрушает последний объект, не уменьшая ёмкость.
Самописный динамический массив‑контейнер (Vector)
Храните отдельно буфер, size и capacity. emplace_back конструирует объект в сырой памяти, push_back делегирует ему копирование, pop_back разрушает последний объект, не уменьшая ёмкость.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Условие
Необходимо написать собственную реализацию контейнера Vector, который будет вести себя так же, как стандартный std::vector. Требуется реализовать основные операции:
push_back— добавление элемента в конец с копированием;pop_back— удаление последнего элемента;emplace_back— размещение нового элемента на месте с использованием perfect forwarding.
# include <iostream>
template<typename T>
class Vector {
public:
// Реализовать: push_back, pop_back, emplace_back
};
Для каждой функции следует обеспечить корректное управление памятью, рост массива при необходимости и корректную работу с объектами любого типа.
Ответ
Храните отдельно буфер, size и capacity. emplace_back конструирует объект в сырой памяти, push_back делегирует ему копирование, pop_back разрушает последний объект, не уменьшая ёмкость.
Ниже учебная реализация трёх операций на C++17. Копирование самого контейнера намеренно запрещено: это не полный аналог всего API std::vector. Для роста T должен допускать перенос или копирование; деструктор не должен выбрасывать исключения.
#include <memory>
#include <utility>
#include <stdexcept>
#include <cstddef>
template<class T>
class Vector {
using A = std::allocator<T>;
using AT = std::allocator_traits<A>;
A alloc;
T* data = nullptr;
std::size_t count = 0, capacity = 0;
public:
Vector() = default;
Vector(const Vector&) = delete;
Vector& operator=(const Vector&) = delete;
~Vector() {
while (count) AT::destroy(alloc, data + --count);
if (data) AT::deallocate(alloc, data, capacity);
}
template<class... Args>
T& emplace_back(Args&&... args) {
if (count < capacity) {
AT::construct(alloc, data + count,
std::forward<Args>(args)...);
return data[count++];
}
const auto limit = AT::max_size(alloc);
if (count == limit) throw std::length_error("Vector");
const auto next = capacity == 0 ? std::size_t{1}
: (capacity > limit / 2 ? limit : capacity * 2);
T* fresh = AT::allocate(alloc, next);
std::size_t moved = 0;
bool tailReady = false;
try {
// Сначала новый элемент: args могут ссылаться на data.
AT::construct(alloc, fresh + count,
std::forward<Args>(args)...);
tailReady = true;
for (; moved < count; ++moved)
AT::construct(alloc, fresh + moved,
std::move_if_noexcept(data[moved]));
} catch (...) {
while (moved) AT::destroy(alloc, fresh + --moved);
if (tailReady) AT::destroy(alloc, fresh + count);
AT::deallocate(alloc, fresh, next);
throw;
}
for (std::size_t i = 0; i < count; ++i)
AT::destroy(alloc, data + i);
if (data) AT::deallocate(alloc, data, capacity);
data = fresh;
capacity = next;
return data[count++];
}
void push_back(const T& value) { emplace_back(value); }
void pop_back() {
if (!count) throw std::out_of_range("empty Vector");
AT::destroy(alloc, data + --count);
}
std::size_t size() const noexcept { return count; }
};
Сырая память не требует конструктора T по умолчанию. Новый элемент создаётся до переноса старых, что важно для push_back со ссылкой на собственный элемент. При исключении новый буфер очищается. Если доступно лишь потенциально бросающее перемещение, старые элементы могут остаться перемещёнными — безусловной строгой гарантии для такого T нет. В этой версии pop_back проверяет пустоту; у std::vector вызов на пустом контейнере не является допустимой операцией.