Самописный динамический массив‑контейнер (Vector)

Храните отдельно буфер, size и capacity. emplace_back конструирует объект в сырой памяти, push_back делегирует ему копирование, pop_back разрушает последний объект, не уменьшая ёмкость.

Короткий ответ

Что ответить на собеседовании

Храните отдельно буфер, 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 вызов на пустом контейнере не является допустимой операцией.

ИИ-помощник для собеседований

Хочешь уверенно проходить собеседования?

Попробуй ИИ-помощник для собеседований: слышит вас и собеседника, анализирует экран, подсказывает ответы в реальном времени, работает без VPN и не попадает в захват экрана.

Подробнее