如何从高性能的输入迭代器返回变体?

How to return a variant from an input iterator with high performance?

本文关键字:返回 迭代器 输入 高性能      更新时间:2023-10-16

我有一些文件格式解码器,它返回一个自定义输入迭代器。此迭代器的值类型(当使用*iter取消引用时(可以是许多令牌类型之一。

下面是一个简化的使用示例:

File file {"/path/to/file"};
for (const auto& token : file) {
// do something with token
}

token怎么可能有多种可能的类型?根据令牌的类型,其有效负载的类型也会发生变化。

在遍历过程中,性能在这里很重要。例如,我不想要任何不必要的分配。这就是为什么迭代器的类型是输入迭代器的原因:一旦你推进迭代器,根据InputIterator标签的要求,以前的令牌就会失效。

到目前为止,我有两个想法:

  1. 使用单个Token类,其中包含所有可能的有效负载(及其公共getter(的私有union和公共类型ID(enum(getter。 用户需要打开此类型 ID 才能知道要调用哪个有效负载获取器:

    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++因为用户仍然可以调用错误的 getter,并且没有什么可以强制执行这一点(除了运行时检查,我想避免出于性能问题(。

  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::variantstd::variant与想法#2相同。

虽然这可能是我在 C 中选择的,但我不喜欢这个解决方案,因为我C++ 中不喜欢这个解决方案,因为用户仍然可以调用错误的 getter,没有什么可以强制执行这一点(除了运行时检查,出于性能问题,我想避免

(。

您可以反转该方法并接受可调用对象,而不是向用户返回迭代器。然后,可以在内部迭代容器并调度正确的类型。这样,用户就不会再通过忽略您标记的工会所携带的信息来犯错误,因为您负责考虑它。

这是一个最小的工作示例来说明我的意思:

#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); });
}

看到它在科利鲁上启动并运行。