У меня есть декодер формата файла, который возвращает пользовательский входной итератор. Тип значения этого итератора (при разыменовании его с помощью *iter
) может быть одним из многих типов токенов.
Вот упрощенный пример использования:
File file {"/path/to/file"};
for (const auto& token : file) {
// do something with token
}
Как это может token
есть несколько возможных типов? В зависимости от типа токена меняется и тип его полезной нагрузки.
Производительность важна здесь во время обхода. Я не хочу никаких ненужных выделений, например. Вот почему тип итератора входной итератор: как только вы продвигаете итератор, предыдущий токен становится недействительным в соответствии с требованиями InputIterator
тег.
Пока у меня есть две идеи:
Используйте один 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 ++, потому что пользователь все еще может вызвать неправильный получатель, и ничто не может принудить это (кроме проверок во время выполнения, которых я хочу избежать из-за проблем производительности) ).
Создать реферат 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.
Хотя это, вероятно, то, что я бы выбрал в 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.
Других решений пока нет …