如何实现符合标准的迭代器/容器?

时间:2012-12-30 08:13:04

标签: c++ for-loop c++11

假设我有一个名为RuleBook的抽象容器类。 RuleBook的用户希望能够对RuleBook进行前向迭代以获得Rule

与标准容器不同,此处对具体子类的内存布局没有限制。相反,由子类的实现者来遵守RuleBook的前向迭代要求,并根据自己的数据结构满足此要求。

我认为RuleBook应该包含纯虚拟begin()end(),因此它可以与基于范围的一起使用,但我遇到了几个问题。

begin()和end()的签名应该是什么? BasketballRules和CompanyRules应如何实施?

当迭代器超过最后一项时,我们如何处理结束条件?

在下面的示例中,您可以假设m_rpm_rpp仅指向一个规则。我们想要为guts返回某种迭代器(比如Rule*)。我们还可以假设Foo的所有子类将在各种数据结构中包含Rule,这将是实现者的一时兴起。

如果我使用Rule*作为我的迭代器并使用null_ptr作为我的超越端点来实现整个事情,那么这是否符合STL?

我目前正在研究自定义迭代器,但我甚至不确定这个问题是否适合该范例,因为每个子类必须有效地定义迭代的内容。

CODE

struct RuleBook
{
  // virtual Rule* begin() = 0;
  // virtual Rule* end() = 0; 
};

struct CompanyRules :
    public RuleBook
{
    Rule m_r;
};

struct BasketballRules :
    public RuleBook
{
    // return the three Rules, one-at-a-time, in succession
    Rule   m_r;
    Rule*  m_rp;
    Rule** m_rpp;
};

int
main( int argv, char* argc[] )
{
}

4 个答案:

答案 0 :(得分:2)

这很难做对。

  

begin()和end()的签名应该是什么?

没有太多选择,他们几乎必须像

RuleBook::iterator begin();
RuleBook::iterator end();

(如果需要,添加const重载)

  

应如何实施BasketballRules和CompanyRules?

小心:)

  

当迭代器超过最后一项时,我们如何处理结束条件?

您正确设计了迭代器类型,以便它正常工作。您需要一个迭代器类型,可以比较相等性并可以递增。当你有一个容器中最后一项的迭代器并且你递增它时,它必须等于过去的迭代器。

  

如果我使用Rule *作为我的迭代器和null_ptr作为我的超越端点来实现整个事情,那么这是否符合STL?

没有。如果你的迭代器类型只是Rule*,那么递增迭代器不会移动到下一个Rule,它只指向内存中的下一个位置,它甚至可能不是Rule个对象,导致未定义的行为。例如对于BasketballRules,如果Rule*指向m_r并递增它,则表示您没有指向有效的Rule对象,而是指向{{1即m_rp,并且取消引用它是未定义的行为。

此外,如果您继续增加Rule*,则永远不会达到过去的Rule*值。

我给Yakk的回答是一个upvote因为它是一个看似合理的实现,但很难做对。有许多事情要考虑并包括在多态接口中,例如如果您使用nullptr比较两个==对象,其中一个指向RuleBook::iterator而另一个指向CompanyRules,会发生什么情况呢?对于多态迭代器,等式是如何工作的?

如果将BasketballRules对象的迭代器分配给BasketballRules对象的迭代器,会发生什么?您需要为多态类型提供“深层复制”。

对于每个容器,您需要一个不同的派生迭代器 - impl类型,CompanyRules容器的迭代器类型需要知道关于CompanyRule类型的所有内容,依此类推每个派生容器类型。每个具体的迭代器 - impl类型都需要将几乎整个迭代器接口实现为虚函数。实施它的难度表明设计存在问题。

更简单的设计是每个派生容器类型管理相同类型的实际物理容器。特定于每个派生容器的代码仅包括在派生对象的内容改变时更新列表的内容。迭代器类型是直截了当的,非多态的,例如

CompanyRule

答案 1 :(得分:1)

确切的签名无关紧要,因为基于范围的for循环是根据所使用的表达式定义的。如果找到beginend成员函数,则会调用它们,如__range.begin()__range.end()。无关紧要的签名示例是,只要可以调用.begin().end()(这意味着参数必须具有默认值),这些成员函数可以具有任意数量和类型的参数。

如果类型没有beginend成员函数,则使用表达式begin(__range)end(__range)。 (其中__range auto &&__range是使用您在for-for-loop中使用的表达式初始化的nullptr。因此,只要参数依赖查找有效,确切的签名就会很重要。


  

应如何实施BasketballRules和CompanyRules?

     

当迭代器超过最后一项时,我们如何处理结束条件?

这些是与range-base-for-loops工作方式不同的问题。您应该具体询问有关这些问题的其他问题。

但是如果你使用指针作为迭代器,那么使用Rule*作为结束迭代器是不合适的,因为递增最后一个有效指针不会给你一个空指针;它会给你一个超出范围结束的指针。此外,如果您使用{{1}}作为迭代器,那么您不会将实现留给容器类;容器必须维护一系列连续的规则。

答案 2 :(得分:1)

Boost有一些迭代器辅助模板类--crtp,它要求你实现一些方法。基于pImpl的用户将允许迭代器的兼容和虚拟行为。即,pImpl是一个纯虚拟抽象类,迭代器将其工作委托给它。

编写一个纯虚拟pImpl迭代器。这是beginend的返回类型。子类使用具体的pImpl实例创建迭代器的实例,并根据数据的存储方式自定义。

答案 3 :(得分:0)

可能你可以尝试像Alex Allain在他关于基于范围的for循环的教程中解释here的方式。它还有一个示例,用于创建可由基于范围的for循环

迭代的数据结构

所需要的是

  1. .begin()和.end()方法。它们也可以是独立的。
  2. operator!=,++和*重载,以便支持需要执行的迭代器操作。