【问题标题】:iterator with special case implementation具有特殊情况实现的迭代器
【发布时间】:2018-03-31 13:49:36
【问题描述】:

有没有办法构建一个具有两种实现的迭代器类:包含任意数量元素的容器的通用实现和容器包含单个元素但不使用虚函数和动态的特殊情况(非常快)实现多态性?

目前,我有:

struct Container { 
    struct FastIterator;
    struct SlowIterator;
    void add(...) { ... }
    SlowIterator begin_slow() { ... }
    FastIterator begin_fast() { ... }
};

相反,我想要:

struct Container { 
    struct Iterator;
    void add(...) { ... }
    Iterator begin() { // select between fast and slow based on the contents of the container }
};

这样:

void f() {
    Container c;
    c.add(...);
    Container::Iterator it = c.begin(); // uses FastIterator hidden by the Iterator type
}

void f2() {
    Container c;
    c.add(...);
    c.add(...);
    Container::Iterator it = c.begin(); // use SlowIterator hidden by the iterator type
}

当然,显而易见的方法是在迭代器实现中使用虚函数或委托来从一种情况切换到另一种情况,但是我测试过,与直接使用慢/快相比,这会大大减慢迭代速度迭代器。

由于在调用 begin() 期间可以获得决定使用哪个实现的所有信息,我认为有一种方法可以使用某种编译时多态性/技巧来避免任何类型的间接。

另外,我真的不希望用户必须决定它应该调用 begin_fast() 还是 begin_slow(),这应该由 Iterator 类自动处理和隐藏。

有办法吗?

谢谢

【问题讨论】:

  • 顺便说一句,我愿意使用任何类型的模板、CRTP 或基于 boost 的解决方案,只要它不需要每个迭代器操作的虚拟调用。

标签: c++ c++11 boost iterator


【解决方案1】:

当然。

您的容器变成std::variant 两种不同状态,“单元素”状态和“多元素”状态(可能还有“零元素”状态)。

成员函数add可以将零元素或单元素容器转换为单元素或多元素函数。同样,remove 在某些情况下可能会做相反的事情。

变体本身没有beginend。相反,用户必须 std::visit 使用可以接受其中任何一个的函数对象。

template<class T>
struct Container:
  std::variant<std::array<T,0>, std::array<T,1>, std::vector<T>>
{
  void add(T t) {
    std::visit(
      overload(
        [&](std::array<T,0>& self) {
          *this = std::array<T,1>{{std::move(t)}};
        },
        [&](std::array<T,1>& self) {
          std::array<T,1> tmp = std::move(self);
          *this = std::vector<T>{tmp[0], std::move(t)};
        },
        [&](std::vector<T>& self) {
          self.push_back( std::move(t) );
        }
      ),
      *this
    );
  }
};

boost 有一个 variant,其工作方式类似。 overload 只是

struct tag {};
template<class...Fs>
struct overload_t {overload_t(tag){}};
template<class F0, class F1, class...Fs>
struct overload_t: overload_t<F0>, overload_t<F1, Fs...> {
  using overload_t<F0>::operator();
  using overload_t<F1, Fs...>::operator();

  template<class A0, class A1, class...Args>
  overload_t( tag, A0&&a0, A1&&a1, Args&&...args ):
    overload_t<F0>( tag{}, std::forward<A0>(a0)),
    overload_t<F1, Fs...>(tag{}, std::forward<A1>(a1), std::forward<Args>(args)...)
  {}
};

template<class F>
struct overload_t:F {
  using F::operator();
  template<class A>
  overload_t( tag, A&& a ):F(std::forward<A>(a)){}
};

template<class...Fs>
overload_t<std::decay_t<Fs>...> overload(Fs&&...fs) {
  return {tag{}, std::forward<Fs>(fs)...};
}

overload 中更简单:

template<class...Fs>
struct overload:Fs{
  using Fs::operator();
};
template<class...Fs>
overload->overload<Fs...>;

并使用{} 而不是()

中使用这个看起来像:

Container<int> bob = get_container();

std::visit( [](auto&& bob){
  for (int x:bob) {
    std::cout << x << "\n";
  }
}, bob );

对于 0 和 1 的情况,编译器将准确知道循环的大小。

中,您必须编写一个外部模板函数对象而不是内联 lambda。

您可以将variant 部分移出Container 并移到begin 返回的内容中(在迭代器内部),但这需要复杂的分支迭代器实现或调用者访问迭代器。并且由于开始/结束迭代器类型可能是绑定的,因此您无论如何都希望返回一个范围,这样访问才有意义。无论如何,这会让你回到容器解决方案的一半。

您也可以在variant 之外实现此功能,但作为一般规则,早期对变量的操作不能在同一代码范围内更改后面的类型。它可以用于调度以“连续传递样式”传递的可调用对象,其中两种实现都将被编译,但在运行时选择一个(通过分支)。编译器可能会意识到访问将关闭哪个分支并且死代码消除另一个,但另一个分支仍然需要是有效代码。

如果您想要完全动态类型的对象,您将至少损失 2 到 10 倍的速度(这是支持此功能的语言所做的),这很难通过一个元素循环的迭代效率来恢复。这将与在返回的迭代器中存储等效的变体(可能是虚拟接口或其他)并使其在运行时复杂地处理分支有关。由于您的目标是性能,因此这是不切实际的。

理论上,C++ 可以根据对变量的操作来更改变量的类型。即,一种理论语言,其中

Container c;

是“空容器”类型,则:

c.add(foo);

现在c 将静态类型更改为“单元素容器”,然后

c.add(foo);

并且c 将静态类型更改为“多元素容器”。

但这不是 C++ 类型模型。你可以像上面那样模拟它(在运行时),但它不一样。

【讨论】:

  • 感谢您的完整回答。但是,我真的很想保留迭代器和开始/结束的概念,因为该类的用户可能会更习惯这种编程。此外,我希望仍然能够通过使用迭代器在此容器中使用 STL 算法。您认为可以在迭代器级别使用相同的方法吗?您提到必须检查这对迭代器是否兼容(这是隐含的范围,但不是单独的迭代器对......),您将如何实现这一点?再次感谢
  • @YoYo 如果您关心性能,就没有办法做到这一点。所以不行。在每个操作上对两种迭代器进行类型擦除的成本将超过单个元素的“快速迭代器”案例的任何合理收益,以及您不想在类型系统中描述哪种情况的决定你在意味着你正在强制代码检查每个操作。 也许运气好的话,你可以让编译器解决并优化分支,但这既脆弱又需要大量的试验和错误
猜你喜欢
  • 2021-11-10
  • 2023-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-23
  • 1970-01-01
  • 2017-10-05
  • 2013-04-05
相关资源
最近更新 更多