多态转换迭代器

时间:2013-07-18 10:56:01

标签: c++ iterator

考虑以下(简化)场景:

class edgeOne {
  private:
    ...
  public:
    int startNode();
    int endNode();
};

class containerOne {
  private:
    std::vector<edgeOne> _edges;
  public:
    std::vector<edgeOne>::const_iterator edgesBegin(){
      return _edges.begin();
    };
    std::vector<edgeOne>::const_iterator edgesEnd(){
      return _edges.end();
    };
};

class edgeTwo {
  private:
    ...
  public:
    int startNode();
    int endNode();
};

class containerTwo {
  private:
    std::vector<edgeTwo> _edges;
  public:
    std::vector<edgeTwo>::const_iterator edgesBegin(){
      return _edges.begin();
    };
    std::vector<edgeTwo>::const_iterator edgesEnd(){
      return _edges.end();
    };
};

即,我有两个大部分相同的边缘类型和两个大多数相同的容器类型。我可以单独迭代各种类型。到目前为止,很好。

但现在我的用例如下:根据一些标准,我得到一个containerOne或一个containerTwo对象。我需要迭代边缘。但由于类型不同,我不能轻松这样做而不需要重复代码。

所以我的想法如下:我想要一个具有以下属性的迭代器:   - 关于其遍历行为,它的行为类似于std::vector<edgeOne>::const_iteratorstd::vector<edgeTwo>::const_iterator,具体取决于它的初始化方式。   - 而不是返回const edgeOne &const edgeTwo &operator*应返回std::pair<int,int>,即应用转化。

我找到了Boost.Iterator Library,特别是:

  • iterator_facade,它有助于构建符合标准的迭代器和
  • transform_iterator,可用于将edgeOneedgeTwo转换为std::pair<int,int>, 但我不完全确定完整的解决方案应该如何。如果我自己构建几乎整个迭代器,使用transform_iterator有什么好处,还是只会使解决方案更加重量?

我想迭代器只需要存储以下数据:

  • 一个标志(bool现在已足够,但如果需要,可能更容易扩展枚举值),指示值类型是edgeOne还是edgeTwo。
  • 一个union,包含两个迭代器类型的条目(只会访问与该标志匹配的那个)。

其他任何东西都可以即时计算。

我想知道是否存在这种多态行为的现有解决方案,即迭代器实现将两个(或更多)底层迭代器实现与相同的值类型相结合。如果存在这样的事情,我可以用它来组合两个transform_iterator

调度(即决定是否需要访问containerOne或containerTwo对象)可以通过独立函数轻松完成......

有关此问题的任何想法或建议?

3 个答案:

答案 0 :(得分:3)

如何制作edgeOne&amp; edgeTwo多态?并在容器中使用指针?

class edge
class edgeOne : public edge
class edgeTwo : public edge

std::vector<edge*>

答案 1 :(得分:2)

  

根据一些标准,我得到一个containerOne或一个containerTwo对象。我需要迭代边缘。但由于类型不同,我不能轻易地在没有代码重复的情况下这样做。

通过“代码重复”,您的意思是源代码还是目标代码?是否有理由通过一个简单的模板?如果您担心构成“代码重复”的两个模板实例,您可以将大部分处理移动到一个非线性的非模板do_whatever_with(int, int)支持函数....

template <typename Container>
void iterator_and_process_edges(const Container& c)
{
    for (auto i = c.edgesBegin(); i != c.edgesEnd(); ++i)
        do_whatever_with(i->startNode(), i->endNode());
}

if (criteria)
    iterate_and_process_edges(getContainerOne());
else
    iterate_and_process_edges(getContainerTwo());

  

我最初的目的是隐藏只需访问开始和结束节点的代码的调度功能。调度是通用的,而循环内部发生的事情则不是,所以IMO这是分离两者的一个很好的理由。

不确定我是否跟着你,但我正在努力。所以,“只需要访问开始和结束节点的代码”。目前尚不清楚访问是否意味着获取容器元素的startNode和endNode,或者使用这些值。我已经考虑了使用它们的do_whatever_with函数,因此通过消除,您的请求似乎想要隔离从Edge中提取节点的代码 - 在下面的仿函数中完成:

template <typename Edge>
struct Processor
{
    void operator()(Edge& edge) const
    {
        do_whatever_with(edge.startNode(), edge.endNode());
    }
};

然后可以将该仿函数应用于Container中的每个节点:

template <typename Container, class Processor>
void visit(const Container& c, const Processor& processor)
{
    for (auto i = c.edgesBegin(); i != c.edgesEnd(); ++i)
        processor(*i);
}

“从只需要访问开始和结束节点的代码隐藏调度功能” - 在我看来有各种级别的调度 - 在标准的基础上,然后在迭代的基础上(每一层功能)调用在某种意义上是“调度”)但是再次通过消除我认为它是上面的迭代的隔离,你正在追求。

if (criteria)
    visit(getContainerOne(), Process<EdgeOne>());
else
    visit(getContainerTwo(), Process<EdgeTwo>());
  

调度是通用的,而循环内部发生的事情则不是,所以IMO这是分开两者的一个很好的理由。

不能说我同意你的意见,但这取决于你是否能看到我的第一种方法存在任何维护问题(对我来说看起来很简单 - 比最新版本更少的一层并且相当不那么繁琐)或者一些潜力重用。上面的visit实现是可重用的,但重用单个for循环(如果你有C ++ 11则会进一步简化)对我来说没用。

你对这种模块化更熟悉,还是我完全误解了你的需求?

答案 2 :(得分:1)

template<typename T1, typename T2>
boost::variant<T1*, T2*> get_nth( boost::variant< std::vector<T1>::iterator, std::vector<T2>::iterator > begin, std::size_t n ) {
  // get either a T1 or a T2 from whichever vector you actually have a pointer to
}

// implement this using boost::iterator utilities, I think fascade might be the right one
// This takes a function that takes an index, and returns the nth element.  It compares for
// equality based on index, and moves position based on index:
template<typename Lambda>
struct generator_iterator {
  std::size_t index;
  Lambda f;
};
template<typename Lambda>
generator_iterator< typename std::decay<Lambda>::type > make_generator_iterator( Lambda&&, std::size_t index=0 );

boost::variant< it1, it2 > begin; // set this to either one of the begins
auto double_begin = make_generator_iterator( [begin](std::size_t n){return get_nth( begin, n );} );
auto double_end = double_begin + number_of_elements; // where number_of_elements is how big the container we are type-erasing

现在我们有一个迭代器可以迭代一个或另一个,并返回boost::variant<T1*, T2*>

然后我们可以编写一个帮助函数,使用访问者从返回的variant中提取所需的两个字段,并将其视为ADT。如果您不喜欢ADT,您可以编写一个包装variant并提供方法的类,甚至将get_nth更改为不那么通用,而是将struct与您的数据一起返回产生的。

每次访问都会有相应的分支,但此计划中没有virtual函数开销。它当前需要一个auto类型的变量,但是您可以编写一个显式函子来替换lambda [begin](std::size_t n){return get_nth( begin, n );},甚至该问题也会消失。

更简单的解决方案:

编写一个迭代每个容器的for_each函数,并将处理后的数据传递给传入的函数。

struct simple_data { int x,y; };
std::function<std::function<void(simple_data)>> for_each() const {
  auto begin = _edges.begin();
  auto end = _edges.end();
  return [begin,end](std::function<void(simple_data)> f) {
    for(auto it = begin; it != end; ++it) {
      simple_data d;
      d.x = it->getX();
      d.y = it->getY();
      f(d);
    }
  };
};

和另一个相似。我们现在可以通过调用foo->foreach()( [&](simple_data d) { /*code*/ } );来迭代内容而不关心细节,因为我将foreach填入std::function返回值而不是直接执行,我们可以传递这个概念循环到另一个函数。

正如评论中所提到的,其他解决方案可以包括使用boost的类型擦除迭代器包装器,或编写返回simple_data的python样式生成器可变生成器。您也可以直接使用boost迭代器创建函数来创建boost::variant<T1, T2>上的迭代器。