考虑以下(简化)场景:
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_iterator
或std::vector<edgeTwo>::const_iterator
,具体取决于它的初始化方式。
- 而不是返回const edgeOne &
或const edgeTwo &
,operator*
应返回std::pair<int,int>
,即应用转化。
我找到了Boost.Iterator Library,特别是:
edgeOne
和edgeTwo
转换为std::pair<int,int>
,
但我不完全确定完整的解决方案应该如何。如果我自己构建几乎整个迭代器,使用transform_iterator有什么好处,还是只会使解决方案更加重量?我想迭代器只需要存储以下数据:
union
,包含两个迭代器类型的条目(只会访问与该标志匹配的那个)。其他任何东西都可以即时计算。
我想知道是否存在这种多态行为的现有解决方案,即迭代器实现将两个(或更多)底层迭代器实现与相同的值类型相结合。如果存在这样的事情,我可以用它来组合两个transform_iterator
。
调度(即决定是否需要访问containerOne或containerTwo对象)可以通过独立函数轻松完成......
有关此问题的任何想法或建议?
答案 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>
上的迭代器。