Как вернуть вариант из входного итератора с высокой производительностью?

У меня есть декодер формата файла, который возвращает пользовательский входной итератор. Тип значения этого итератора (при разыменовании его с помощью *iter) может быть одним из многих типов токенов.

Вот упрощенный пример использования:

File file {"/path/to/file"};

for (const auto& token : file) {
// do something with token
}

Как это может token есть несколько возможных типов? В зависимости от типа токена меняется и тип его полезной нагрузки.

Производительность важна здесь во время обхода. Я не хочу никаких ненужных выделений, например. Вот почему тип итератора входной итератор: как только вы продвигаете итератор, предыдущий токен становится недействительным в соответствии с требованиями InputIterator тег.

Пока у меня есть две идеи:

  1. Используйте один Token класс с частным union всех возможных полезных нагрузок (с их общедоступными получателями) и общедоступным идентификатором типа (enum) добытчик.
    Пользователь должен включить этот тип идентификатора, чтобы узнать, какой метод получения полезной нагрузки следует вызвать:

    for (const auto& token : file) {
    switch (token.type()) {
    case Token::Type::APPLE:
    const auto& apple = token.apple();
    // ...
    break;
    
    case Token::Type::BANANA:
    const auto& banana = token.banana();
    // ...
    break;
    
    // ...
    }
    }
    

    Хотя это, вероятно, то, что я бы выбрал в C, я не фанат этого решения в C ++, потому что пользователь все еще может вызвать неправильный получатель, и ничто не может принудить это (кроме проверок во время выполнения, которых я хочу избежать из-за проблем производительности) ).

  2. Создать реферат Token базовый класс, который имеет accept() метод для принятия посетителя, и несколько конкретных классов (по одному для каждого типа полезной нагрузки), наследующих этот базовый класс. В объекте итератора создайте экземпляр одного из каждого конкретного класса во время создания. Также есть Token *token член. При выполнении итерации заполните соответствующий предварительно выделенный объект полезной нагрузки и установите this->token = this->specificToken, Делать operator*() вернуть this->token (ссылка на). Попросите пользователя использовать посетителя во время итерации (или, что еще хуже, используйте dynamic_cast):

    class MyVisitor : public TokenVisitor {
    public:
    void visit(const AppleToken& token) override {
    // ...
    }
    
    void visit(const BananaToken& token) override {
    // ...
    }
    };
    
    TokenVisitor visitor;
    
    for (const auto& token : file) {
    token.accept(visitor);
    }
    

    Это вводит дополнительные вызовы функций для каждого токена, по крайней мере, один из которых является виртуальным, но это может быть не конец света; Я остаюсь открытым для этого решения.

Есть ли другое интересное решение? Я считаю, что возвращение boost::variant или же std::variant так же, как идея № 2.

2

Решение

Хотя это, вероятно, то, что я бы выбрал в C, я не фанат этого решения в C ++, потому что пользователь все еще может вызвать неправильный получатель, и ничто не может принудить это (кроме проверок во время выполнения, которых я хочу избежать из-за проблем производительности) ).

Вы можете изменить подход и принять вызываемый объект вместо того, чтобы возвращать итератор пользователю. Затем вы можете выполнить внутреннюю итерацию контейнера и отправить правильный тип. Таким образом, пользователи больше не могут совершать ошибки, игнорируя информацию, содержащуюся в вашем помеченном профсоюзе, поскольку вы сами принимаете это во внимание.

Вот минимальный рабочий пример, чтобы показать, что я имею в виду:

#include <vector>
#include <utility>
#include <iostream>

struct A {};
struct B {};

class C {
struct S {
enum { A_TAG, B_TAG } tag;
union { A a; B b; };
};

public:
void add(A a) {
S s;
s.a = a;
s.tag = S::A_TAG;
vec.push_back(s);
}

void add(B b) {
S s;
s.b = b;
s.tag = S::B_TAG;
vec.push_back(s);
}

template<typename F>
void iterate(F &&f) {
for(auto &&s: vec) {
if(s.tag == S::A_TAG) {
std::forward<F>(f)(s.a);
} else {
std::forward<F>(f)(s.b);
}
}
}

private:
std::vector<S> vec;
};

void f(const A &) {
std::cout << "A" << std::endl;
}

void f(const B &) {
std::cout << "B" << std::endl;
}

int main() {
C c;
c.add(A{});
c.add(B{});
c.add(A{});
c.iterate([](auto item) { f(item); });

}

Смотрите это и работает на Coliru.

2

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]