10 Sequências

Há dois principais tipos de sequências: lista e colônia4.

Listas são estruturas de dados cujos elementos podem ser acessados sequencialmente preservando a ordem destes.

Colônias, por sua vez, sacrificam a ordem dos elementos para garantir outras propriedades desejadas.

10.1 Listas

10.1.1 Modelo abstrato e operações em listas

Para definir o nosso modelo abstrato de lista, vamos começar pelas operações desejáveis.

Operações comuns em listas.
Operação Parâmetros Descrição
Criar Capacidade Cria uma lista com capacidade especificada (potencialmente ilimitada5).
Copiar Cria uma cópia da lista.
Esvaziar Esvazia a lista.
Acessar Posição Acessa um elemento da lista dada uma posição.
Inserir Elemento, Posição Insere um elemento numa posição específica da lista.
Remover Posição Remove o elemento numa posição específica da lista.
Concatenar Lista Concatena outra lista no fim da lista.
Separar Posição Separa a lista em duas a partir duma determinada posição.

Além dessas operações, há outras mais triviais, como consultar o tamanho, consultar a capacidade, verificar se está cheia, e verificar se está vazia.

Em C++17, podemos definir uma interface6 básica:

template <typename T> class list {
public:
  explicit list(std::size_t capacity);
  ~list() noexcept;

  list(const list&);
  list(list&&) noexcept;

  auto operator=(const list&) -> list&;
  auto operator=(list&&) noexcept -> list&;

  auto empty() -> void;

  auto size() const -> std::size_t;
  auto capacity() const -> std::size_t;

  auto is_empty() const -> bool;
  auto is_full() const -> bool;

  auto at(std::size_t) -> T&;
  auto front() -> T&;
  auto back() -> T&;

  auto insert(std::size_t, T) -> void;
  auto remove(std::size_t) -> void;

  auto concat(list<T>) -> void;
  auto split(std::size_t) -> list<T>;
};

Uma vez que levantamos as operações desejáveis da lista, veremos diferentes estruturas de dados que contemplam essa finalidade. Veremos também que cada uma delas apresentará diferentes vantagens e desvantagens em termos de complexidade de tempo.

Além disso, dependendo da estrutura que escolhermos, mudaremos alguns detalhes da nossa interface para melhor adequá-la ao contexto.

10.1.2 Implementações de listas

Nas implementações, não vamos usar os containers da STL (std::vector, std::list, etc) por motivos didáticos.

10.1.2.1 Lista contígua estática

template <typename T, std::size_t N> class list {
public:
  list() = default;
  // ...
private:
  using storage_type = std::aligned_storage_t<sizeof(T), alignof(T)>;
  std::array<storage_type, N> values_;
  std::size_t size_ = 0u;
};

Não precisamos implementar o construtor.

Exercício: Por que o construtor padrão (gerado pelo compilador) é suficiente neste caso?

Limpando a lista:

template <typename T, std::size_t N>
auto list<T, N>::empty() {
  if constexpr (!std::is_trivially_destructible_v<T>)
    for (std::size_t i = 0; i < size_; ++i)
      std::destroy_at(std::address_of(values_[i]));
  size_ = 0;
}

Com esta operação, a implementação do destrutor torna-se bastante simples:

template <typename T, std::size_t N>
list<T, N>::~list() noexcept {
  empty();
}

Checando as margens:

template <typename T, std::size_t N>
auto list<T, N>::is_empty() const -> bool {
  return size_ == 0;
}

template <typename T, std::size_t N>
auto list<T, N>::is_full() const -> bool {
  return size_ == N;
}

Os construtores de cópia e movimentação são triviais:

template <typename T, std::size_t N>
list<T, N>::list(const list& source) {
  for (std::size_t i = 0; i < source.size_; ++i)
    ::new (std::address_of(values_[i])) T(source.values_[i]);
  size_ = source.size_;
}

template <typename T, std::size_t N>
list<T, N>::list(list&& source) noexcept {
  for (std::size_t i = 0; i < source.size_; ++i)
    ::new (std::address_of(values_[i])) T(std::move(source.values_[i]));
  size_ = std::exchange(source.size_, 0u);
}

Exercícios:

  1. Implementar as operações de atribuição (cópia e movimentação) eficientemente.
  2. Implementar versão destas operações (construtores e atribuições) garantias de exceção fortes nestas operações (sem utilizar funções prontas da biblioteca padrão).

10.1.3 Contíguo dinâmico

template <typename T> class list {
public:
  // ...
private:
  using storage_type = std::aligned_storage_t<sizeof(T), alignof(T)>;
  std::unique_ptr<storage_type[]> values_;
  std::size_t capacity_;
  std::size_t size_ = 0u;
};

10.1.3.1 Simplesmente encadeada

10.1.3.2 Duplamente encadeada

10.1.3.3 Possíveis aprimoramentos

10.2 Iterador

10.3 Colônia


  1. Em inglês, colony.↩︎

  2. Ilimitada conceitualmente; toda estrutura digital possui limitações de armazenamento.↩︎

  3. Não no sentido de orientação à objeto, mas em um sentido mais amplo.↩︎