Carregando
Todos os artigos
Programação 6 min de leitura

std::vector em C++: Tamanho, Capacidade e Crescimento Seguro

Entenda como std::vector cresce em C++: a diferença entre tamanho e capacidade, quando usar reserve, por que resize é diferente e como evitar referências inválidas.

nbkhope
Nesta página

std::vector em C++: Tamanho, Capacidade e Crescimento Seguro

Você tem uma lista de notas, mas não sabe quantas haverá. Um array embutido de C++ tem um número fixo de elementos; std::vector pode crescer quando você acrescenta notas. Essa flexibilidade traz perguntas importantes: qual é a diferença entre tamanho e capacidade? reserve cria elementos? O que acontece com uma referência quando o vetor muda de área na memória?

Este guia continua Arrays em C++. Aquele artigo compara arrays embutidos, std::array e std::vector; aqui vamos trabalhar com o vetor em mais detalhes. Todas as saídas de exemplo foram conferidas com GCC 14.1 no Windows, usando C++20.

Um vector tem elementos ocupados e espaço reservado; o crescimento pode movê-los para outra área da memória.

Adicione e leia elementos

Salve como main.cpp, compile com g++ -std=c++20 -Wall -Wextra main.cpp -o main e execute o programa gerado:

1#include <cstddef>
2#include <iostream>
3#include <vector>
4
5int main() {
6    std::vector<int> notas{90, 85, 77};
7    notas.push_back(92);
8
9    std::cout << "quantidade: " << notas.size() << '\n';
10    std::cout << "primeira: " << notas.front() << '\n';
11    std::cout << "ultima: " << notas.back() << '\n';
12
13    for (std::size_t i = 0; i < notas.size(); ++i) {
14        if (i != 0) std::cout << ' ';
15        std::cout << notas[i];
16    }
17    std::cout << '\n';
18}
1quantidade: 4
2primeira: 90
3ultima: 92
490 85 77 92

push_back acrescenta um elemento ao final. size() conta os elementos que realmente existem. Os índices começam em zero: notas[3] é a quarta nota. Só chame front() ou back() quando o vetor não estiver vazio.

Tamanho não é capacidade

Capacidade é a quantidade de elementos que cabem na área atual do vetor antes de ele precisar reservar mais memória. Ela é pelo menos igual ao tamanho, mas a capacidade extra não contém elementos prontos para uso.

1tamanho = 3, capacidade = 5
2
3índice:    0    1    2    3    4
4espaço:  [ 10 ][ 20 ][ 30 ][ -- ][ -- ]
5          elementos atuais  | espaço extra

As caixas nos índices 3 e 4 representam apenas espaço disponível. Elas ficam fora do tamanho do vetor: valores[3] é inválido até que um quarto elemento seja adicionado. Use push_back ou resize para criar esse elemento.

Meça o crescimento sem presumir uma regra fixa

Execute este programa para observar as mudanças de tamanho e capacidade:

1#include <iostream>
2#include <vector>
3
4int main() {
5    std::vector<int> valores;
6    valores.reserve(5);
7    std::cout << "inicio: tamanho=" << valores.size()
8              << " capacidade=" << valores.capacity() << '\n';
9
10    for (int valor = 1; valor <= 6; ++valor) {
11        const auto anterior = valores.capacity();
12        valores.push_back(valor * 10);
13        if (valores.capacity() != anterior) {
14            std::cout << "memoria cresceu no tamanho " << valores.size()
15                      << ": capacidade=" << valores.capacity() << '\n';
16        }
17    }
18
19    std::cout << "fim: tamanho=" << valores.size()
20              << " capacidade=" << valores.capacity() << '\n';
21}

Na instalação com GCC 14.1 usada neste artigo, a saída foi:

inicio: tamanho=0 capacidade=5
memoria cresceu no tamanho 6: capacidade=10
fim: tamanho=6 capacidade=10

O padrão C++ garante que reserve(5) fornece capacidade de pelo menos cinco e não altera o tamanho. Ele não obriga todas as implementações a escolher capacidade 5 nem a crescer até 10. A observação útil é que as primeiras cinco adições cabem sem uma nova alocação; quando a capacidade era cinco, a sexta exige mais espaço. Esta é uma medição do crescimento em uma implementação, não um teste de velocidade.

Sequência ilustrativa para um vetor com capacidade exatamente cinco: três elementos ocupados, depois cinco, e então um sexto que exige outra área de memória com capacidade de pelo menos seis. As referências antigas ficam inválidas após a realocação.

O desenho começa com capacidade exatamente cinco; por isso, a terceira etapa exige realocação. Implementações reais podem escolher uma capacidade maior, então a nova aparece apenas como “pelo menos seis”. Guarde um índice e consulte o elemento novamente após o crescimento, em vez de usar uma referência para a área antiga.

reserve e resize têm funções diferentes

É fácil confundir os nomes:

Chamada em um vetor vazioTamanho depoisO que mudou?
valores.reserve(5)0Há espaço para pelo menos cinco elementos; nenhum foi criado
valores.resize(5)5Cinco elementos int agora existem, inicializados com zero
Diagrama lado a lado: reserve(5) deixa cinco espaços livres e tamanho zero; resize(5) cria cinco elementos de valor zero e tamanho cinco. Um destaque mostra que crescer além da capacidade pode invalidar referências antigas.

As cinco caixas mostram espaço possível à esquerda e elementos existentes à direita. As duas chamadas podem reservar memória, mas só resize torna esses índices válidos. Se um push_back posterior passar da capacidade e causar realocação, obtenha de novo ponteiros, referências ou iteradores a partir do vetor.

Use reserve quando pretende acrescentar aproximadamente essa quantidade de elementos e quer evitar algumas realocações. Use resize quando precisa dos elementos agora, talvez para preenchê-los por índice. Chamar reserve a cada push_back geralmente atrapalha: deixe o vetor cuidar do crescimento, a menos que tenha uma estimativa útil.

1std::vector<int> valores;
2valores.reserve(5);   // tamanho ainda é 0: valores[0] é inválido
3valores.push_back(42); // tamanho agora é 1: valores[0] existe
4valores.resize(3);    // tamanho agora é 3: valores[1] e valores[2] são 0

O crescimento pode invalidar referências e iteradores

O vetor guarda seus elementos lado a lado na memória. Quando precisa de uma área maior, ele pode mover ou copiar os elementos. Se houver realocação, ponteiros, referências e iteradores para os elementos ficam inválidos. Ler por meio de um deles depois disso tem comportamento indefinido. Não tente provar o problema acessando um ponteiro pendurado: um programa que parece funcionar ainda pode estar errado.

Guarde um índice se precisar reencontrar um elemento depois de acrescentar valores e então acesse-o novamente:

1#include <iostream>
2#include <vector>
3
4int main() {
5    std::vector<int> valores{10};
6    const std::size_t indiceInicial = 0;
7
8    for (int valor = 20; valor <= 100; valor += 10) {
9        valores.push_back(valor);
10    }
11
12    std::cout << "primeiro: " << valores.at(indiceInicial) << '\n';
13    std::cout << "quantidade: " << valores.size() << '\n';
14}
primeiro: 10
quantidade: 10

at() verifica o índice e lança std::out_of_range se ele estiver fora do tamanho atual; operator[] não faz essa verificação. O índice ainda precisa apontar para o elemento desejado no vetor atual: inserir ou remover antes dele pode mudar sua posição. reserve pode reduzir realocações, mas não garante que toda alteração futura manterá referências válidas.

Escolha a ferramenta certa

  • Use std::vector quando a quantidade de elementos pode mudar, especialmente ao acrescentar no final.
  • Use std::array quando o tamanho é fixo e conhecido na compilação.
  • Use um array embutido quando precisa conversar com código que exige esse formato ou está aprendendo como arrays e ponteiros funcionam.

Ler elementos de std::vector não é inerentemente lento: assim como em um array, eles ficam lado a lado. O crescimento pode exigir alocação e movimentação, então não confunda o custo de crescer com o custo de acessar. Para a comparação completa e as medições, volte a Arrays em C++.

Experimente

Mude o programa de crescimento para chamar reserve(8) e acrescentar seis valores. Preveja o tamanho final e a capacidade mínima antes de rodar. Depois troque reserve(8) por resize(8) e preveja os oito primeiros valores antes do laço.

Resposta: Depois de reserve(8) e seis adições, o tamanho é 6 e a capacidade é pelo menos 8. Depois de resize(8), o tamanho já é 8 e seus oito elementos int são zero; acrescentar mais seis eleva o tamanho a 14. A capacidade exata depende da implementação.

Para saber mais

Obrigado pela leitura!

Conhece alguém que vai gostar? Mande para essa pessoa.

Mais artigos

Leia a seguir

Programação 5 min de leitura

Como Tratar Erros em Go: Um Guia Prático

Aprenda a tratar erros em Go com exemplos executáveis: retorne um erro, confira o resultado, acrescente contexto com %w e reconheça um arquivo ausente com errors.Is.

nbkhope

Comentários

Seja gentil e específico. Comentários com links de contas novas aparecem depois da análise de um moderador.