Пользовательские итераторы C ++ и проблема диапазона - PullRequest
2 голосов
/ 11 октября 2019

Допустим, у меня есть интерфейс. Интерфейс имеет функции начала и конца, потому что производные классы должны реализовывать функциональность для диапазона. Пользователь будет использовать только интерфейс и не будет знать о реализации производных классов. Я не могу использовать один и тот же итератор для всех производных классов (более конкретно, оператор ++ () отличается), поэтому мне нужно создать абстрактный базовый класс итератора.

class BaseIterator
{
    //...
public:
    virtual Type operator*()
    {
        //Implementation
    }

    virtual bool operator!=(const BaseIterator&)
    {
        //Implementation
    }

    virtual BaseIterator& operator++() = 0; 
}

//Interface
struct Interface
{
    //other pure virtual functions

    virtual BaseIterator& begin() = 0;
    virtual BaseIterator& end() = 0;
}

В конкретных классах, которые наследуют используемый мной интерфейс, скажем, один из них - класс A, у каждого есть свой собственный итератор, который наследуется от BaseIterator, и использует его для реализации функций начала и конца.

class A : public Interface
{
//...

class AIterator : public BaseIterator
    {
        AIterator& operator++()
        {
            //...
        }
    }

    public:

    AIterator& begin() 
    {
        //...
    }

    AIterator& end() 
    {
        //...
    }


}

Аналогично для других производных классов. Проблема возникает, когда я пытаюсь использовать цикл for с полиморфными типами. Например (*)

Interface* c = Interface::makeA(); //assume for simplicity that there is static function in "Interface"

for(auto el : *c)
{
    //do something with el
}

Я получаю ошибку, что не могу создать экземпляр абстрактного класса из-за оператора чистой виртуальной функции ++ (). Я думаю, что причина этого заключается в реализации цикла for-range, который эквивалентен следующему:

auto && __range = range_expression ;
for (auto __begin = __range.begin(), __end = __range.end(); __begin != __end; ++__begin) {
   range_declaration = *__begin;
   loop_statement;
}

Я считаю, что проблема заключается в "auto__begin == __range.begin ()". begin возвращает ссылку на BaseIterator, который из-за автоматического вывода типа удаляется, что в конце концов делает __begin тип BaseIterator, который является абстрактным классом и не может быть создан. Я знаю, что такое поведение может быть реализовано в Java. Я что-то здесь упускаю? Если нет, как бы вы это реализовали, но сохранить функциональность в (*)?

1 Ответ

1 голос
/ 12 октября 2019

Пожалуйста, опубликуйте полный пример, показывающий проблему в будущем. Я предполагаю, что ошибка, с которой вы сталкиваетесь, выглядит примерно так: (https://wandbox.org/permlink/PKop4WMbpuygHoes)

prog.cc:73:19: error: cannot allocate an object of abstract type 'BaseIterator'
   73 |     for(auto&& i: a) {
      |                   ^
prog.cc:3:7: note:   because the following virtual functions are pure within 'BaseIterator':
    3 | class BaseIterator
      |       ^~~~~~~~~~~~
prog.cc:19:27: note:     'virtual BaseIterator& BaseIterator::operator++()'
   19 |     virtual BaseIterator& operator++() = 0;
      |                           ^~~~~~~~
prog.cc:73:19: error: cannot declare variable '__for_begin ' to be of abstract type 'BaseIterator'
   73 |     for(auto&& i: a) {
      |                   ^
prog.cc:73:19: error: cannot allocate an object of abstract type 'BaseIterator'
prog.cc:73:19: error: cannot declare variable '__for_end ' to be of abstract type 'BaseIterator'

Проблема очевидна, поскольку цикл for пытается присвоить возвращаемое значение begin() локальным переменным, которые он может 't.

C ++ не является Java. Цикл for для диапазона либо вызывает функции-члены begin / end, либо вызывает свободные функции, поэтому интерфейс для получения итератора не требуется. Класс должен реализовывать только эти функции, и будет работать диапазон, основанный на. Кроме того, наличие operator!= в базовом классе позволяет сравнивать два итератора разных подтипов. Чтобы проверить это, вам нужно использовать dynamic_castчтобы проверить типы. Вам также нужно уменьшить аргумент в реализации, который явно не является хорошим дизайном.

Наследование не так заметно для подобных проблем в C ++, и универсальный код с использованием шаблонов предпочтителенНапример, в STL. Все алгоритмы будут работать с совместимым итератором без наследования, поскольку тип итератора является шаблономrameter.

Ниже приведен пример использования простого класса Range в качестве контейнера для итератора: #include

struct Range;

struct Iterator {
    Range const* r;
    int current;

    Iterator(Range const* x, int c) : r(x), current(c) {}

    Iterator& operator++() {
        ++current;
        return *this;
    }

    int const& operator*() const {
        return current;
    }
};

bool operator==(Iterator const& x, Iterator const& y) {
    return x.current == y.current;
}

bool operator!=(Iterator const& x, Iterator const& y) {
    return !(x == y);
}

struct Range {
    int min;
    int max;

    Iterator begin() const {
        return Iterator(this, min);
    }

    Iterator end() const {
        return Iterator(this, max+1);
    }
};


int main() {
    Range r{-5, 5};
    for(auto&& i: r) {
        std::cout << i << std::endl;
    }
}

Это упрощенный и неполный пример, демонстрирующий, как заставить его работать сдиапазон для цикла. Если вы хотите реализовать итераторы, соответствующие итераторам в стандартной библиотеке, вам нужно добавить несколько функций-членов и typedefs. Должно быть много информации о том, как написать эти итераторы.

PS: Если вы решите использовать Java-подобный подход с интерфейсами, не забудьте предоставить своим интерфейсам (открытый или защищенный) виртуальный деструктор иобъявите конструкторы копирования как delete, чтобы предотвратить нарезку объектов.

...