Encontrando uma Forma Genérica de Acessar Diversos Contêineres em C++

No C++ moderno, um desafio comum que os desenvolvedores enfrentam é iterar sobre diferentes tipos de contêiner de forma uniforme. Imagine que você tem diversos contêineres: o std::vector padrão, std::list, e uma lista customizada. Cada contêiner tem seu próprio tipo de iterador único, o que pode dificultar a escrita de código limpo e manutenível. Neste post de blog, vamos explorar esse problema e apresentar uma solução elegante para acessar esses contêineres genericamente.

O Problema

Ao tentar acessar itens nesses contêineres diversos, você rapidamente encontra um problema. Na sua classe Foo, você deseja um único iterador que possa interagir com std::vector<int>, std::list<int>, e sua lista customizada. No entanto, como todos esses contêineres retornam tipos de iterador diferentes, suas tentativas de atribuir um iterador resultariam em conflitos de tipo.

Aqui está uma versão simplificada do desafio que você está enfrentando:

class Foo {
public:
    Foo() {
        std::list<int> x;
        std::vector<int> y;
        custom_list<int> z;

        iter = x.begin(); // OU
        iter = y.begin(); // OU
        iter = z.begin(); // Problema: Tipos de iteradores diferentes
    };
private:
    std::iterator<int> iter; // Isso não funcionará
};

A Solução

Para resolver esse problema mantendo seu código elegante e gerenciável, podemos utilizar um conceito conhecido como apagamento de tipo. Essa técnica nos permite criar uma interface genérica para iteradores, fornecendo uma maneira uniforme de acessar itens através de diferentes tipos de contêiner.

Etapa 1: Classe Base de Iterador

Vamos começar definindo uma interface base da qual todos os iteradores herdarão. Isso proporciona uma abordagem comum para interagir com os diferentes tipos de iteradores.

class IIterator {
public:
    virtual ~IIterator() = default;
    virtual int& operator*() = 0; // Operador de desreferência
    virtual IIterator& operator++() = 0; // Operador de incremento
    virtual bool operator!=(const IIterator& other) const = 0; // Operador de comparação
};

Etapa 2: Implementação para Cada Tipo de Contêiner

Em seguida, implementaremos essa interface para os diferentes tipos de contêiner.

Para std::vector

class VectorIterator : public IIterator {
    std::vector<int>::iterator it;
public:
    VectorIterator(std::vector<int>::iterator iterator) : it(iterator) {}
    int& operator*() override { return *it; }
    IIterator& operator++() override { ++it; return *this; }
    bool operator!=(const IIterator& other) const override {
        return this->it != dynamic_cast<const VectorIterator&>(other).it;
    }
};

Para std::list

class ListIterator : public IIterator {
    std::list<int>::iterator it;
public:
    ListIterator(std::list<int>::iterator iterator) : it(iterator) {}
    int& operator*() override { return *it; }
    IIterator& operator++() override { ++it; return *this; }
    bool operator!=(const IIterator& other) const override {
        return this->it != dynamic_cast<const ListIterator&>(other).it;
    }
};

Para Lista Customizada

Assumindo que sua lista customizada que define um iterador está disponível, você o implementaria da mesma forma abaixo:

class CustomListIterator : public IIterator {
    custom_list<int>::iterator it;
public:
    CustomListIterator(custom_list<int>::iterator iterator) : it(iterator) {}
    int& operator*() override { return *it; }
    IIterator& operator++() override { ++it; return *this; }
    bool operator!=(const IIterator& other) const override {
        return this->it != dynamic_cast<const CustomListIterator&>(other).it;
    }
};

Etapa 3: Usando os Iteradores em Foo

Agora, em sua classe Foo, você pode manter um único tipo de ponteiro para a classe base IIterator para lidar com qualquer um dos tipos de contêiner.

class Foo {
public:
    Foo() {
        std::list<int> x;
        std::vector<int> y;
        custom_list<int> z;

        IIterator* iter;

        // Vamos supor que você tenha instâncias de cada contêiner
        iter = new VectorIterator(y.begin());
        // OU
        iter = new ListIterator(x.begin());
        // OU
        iter = new CustomListIterator(z.begin());

        // Agora você pode usar iter como um iterador uniforme
    };
};

Conclusão

Ao utilizar apagamento de tipo através de uma interface comum de iterador, fornecemos uma maneira elegante de manipular diferentes tipos de contêiner em C++. Isso permite um código mais limpo e manutenível, evitando a dor de cabeça de gerenciar múltiplos tipos de iterador.

Para uma exploração mais aprofundada sobre este tópico, considere ler alguns artigos interessantes:

Essa solução não apenas aprimora suas habilidades em C++, mas também ajuda a criar estruturas de código versáteis e reutilizáveis que podem lidar com vários tipos de contêiner de forma integrada.