Implementação Interna de um Container Vector em C++

Estrutura e Variáveis de Membro

Para simular o funcionamento do std::vector, utilizamos a alocação dinâmica de memória no heap. A estrutura base baseia-se em três ponteiros fundamentais que gerenciam o intervalo de dados e a capacidade total alocada.

template <typename T>
class MyVector {
public:
    typedef T* iterator;
    typedef const T* const_iterator;

private:
    iterator _base = nullptr;          // Início do array
    iterator _top = nullptr;           // Fim dos dados válidos (size)
    iterator _capacity_limit = nullptr; // Fim do espaço alocado (capacity)
};

Gerenciamento de Iteradores

Os iteradores em um vector simplificado são ponteiros diretos para os elementos. As funções begin e end delimitam o intervalo de elementos ativos.

iterator begin() { return _base; }
iterator end() { return _top; }

const_iterator begin() const { return _base; }
const_iterator end() const { return _top; }

Capacidade e Acesso a Elementos

O tamenho (size) é a distância entre o topo e a base, enquanto a capacidade é a distância total entre o limite da memória e a base. O acesso via índice utiliza a sobrecarga do operador de colchetes com validação de limites.

size_t size() const { return _top - _base; }
size_t capacity() const { return _capacity_limit - _base; }

T& operator[](size_t index) {
    assert(index < size());
    return *(_base + index);
}

const T& operator[](size_t index) const {
    assert(index < size());
    return *(_base + index);
}

Realocação de Memória (Reserve)

A função reserve é crucial para evitar múltiplas alocações. É necessário realizar uma cópia profunda dos elementos existentes para o novo bloco de memória, garanitndo que objetos complexos (como std::string) não sofram problemas de cópia rasa (shallow copy).

void reserve(size_t new_cap) {
    if (new_cap > capacity()) {
        size_t current_size = size();
        T* new_data = new T[new_cap];

        if (_base) {
            // Cópia profunda via atribuição para evitar problemas com tipos complexos
            for (size_t i = 0; i < current_size; ++i) {
                new_data[i] = _base[i];
            }
            delete[] _base;
        }

        _base = new_data;
        _top = _base + current_size;
        _capacity_limit = _base + new_cap;
    }
}

Inserção e Remoção de Elementos

Ao inserir elementos, devemos estar atentos à "invalidação de iteradores". Se houver realocação, o ponteiro original torna-se inválido, exigindo o cálculo do deslocamento relativo.

iterator insert(iterator pos, const T& val) {
    assert(pos >= _base && pos <= _top);

    if (_top == _capacity_limit) {
        size_t offset = pos - _base;
        size_t next_cap = capacity() == 0 ? 4 : capacity() * 2;
        reserve(next_cap);
        pos = _base + offset;
    }

    iterator cursor = _top;
    while (cursor > pos) {
        *cursor = *(cursor - 1);
        --cursor;
    }

    *pos = val;
    ++_top;
    return pos;
}

void push_back(const T& val) {
    insert(end(), val);
}

iterator erase(iterator pos) {
    assert(pos >= _base && pos < _top);

    iterator cursor = pos + 1;
    while (cursor != _top) {
        *(cursor - 1) = *cursor;
        ++cursor;
    }

    --_top;
    return pos;
}

Redimensionamento Dinâmico (Resize)

O resize ajusta o número de elementos. Se o novo tamanho for maior, preenchemos o espaço com valores padrão; se for menor, apenas movemos o ponteiro de controle.

void resize(size_t n, T val = T()) {
    if (n < size()) {
        _top = _base + n;
    } else {
        if (n > capacity()) {
            reserve(n);
        }
        while (size() < n) {
            push_back(val);
        }
    }
}

Construtores e Gerenciamento de Ciclo de Vida

Implementamos o construtor padrão, o preenchimento inicial e o construtor por intervalo de iteradores (template). O destrutor garante a liberação correta da memória heap.

MyVector() = default;

MyVector(size_t n, const T& val = T()) {
    reserve(n);
    for (size_t i = 0; i < n; ++i) {
        push_back(val);
    }
}

template <class InputIterator>
MyVector(InputIterator first, InputIterator last) {
    while (first != last) {
        push_back(*first);
        ++first;
    }
}

~MyVector() {
    if (_base) {
        delete[] _base;
        _base = _top = _capacity_limit = nullptr;
    }
}

Operação de Atribuição e Swap

Utilizamos a técnica de "Copy and Swap" para implementar o operador de atribuição de forma segura e eficeinte.

void swap(MyVector<T>& other) {
    std::swap(_base, other._base);
    std::swap(_top, other._top);
    std::swap(_capacity_limit, other._capacity_limit);
}

MyVector<T>& operator=(MyVector<T> tmp) {
    this->swap(tmp);
    return *this;
}

Tags: cpp STL vector memory-management templates

Publicado em 7-16 10:51