【问题标题】:How to implement an iterator/container that is standards-compliant?如何实现符合标准的迭代器/容器?
【发布时间】:2012-12-30 08:13:04
【问题描述】:

假设我有一个类似容器的抽象类,名为RuleBookRuleBook 的用户希望能够向前迭代 RuleBook 以获得 Rules。

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

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

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

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

在下面的示例中,您可以假设m_rpm_rpp 各只指向一个规则。我们想为胆量返回某种迭代器(如Rule*)。我们还可以假设 Foo 的所有子类都将在各种数据结构中包含Rule,这取决于实现者的心血来潮。

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

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

代码

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[] )
{
}

【问题讨论】:

  • @MatsPetersson 如果您不想回答,请不要冷嘲热讽或居高临下 - 我上一次正式上学是很久以前的事了
  • @MatsPetersson 好吧,我可以为我的问题编写一个基于指针的解决方案,但这无济于事(我今年 42 岁——我上一次上学大约是 2 年前) - 这会混淆问题。我的问题是,我不明白遵守 STL 迭代器的复杂性。如果我改写我的问题,也许你会更高兴?也许我误解了——所以不是问问题来学习一些东西吗?
  • @MatsPetersson 我为我的抽象道歉 - 我已经改写了 OP
  • @kfmfe04:现在看起来好多了。现在如果所有成员都是Rule,那为什么要声明三个不同的成员呢?为什么不只是一个,最好是std::vector<Rule>
  • 我不确定您是否真的在寻找一般用法,或者允许 iterator 的完全任意定义将这个带回家。在 C++11 中,有一组 巨大的 实用程序可用于在 <iterator_traits><iterator> 中进行利用,您可能会发现它们在您的任务中很有价值。当我必须编写一个模板类来接受任意template< class Iterator > 解决一个不那么微不足道的问题时,它们肯定在我的身边。调查他们。

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


【解决方案1】:

这很难做到。

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

没有太多选择,它们几乎必须是类似的东西

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

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

应如何实施篮球规则和公司规则?

小心:)

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

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

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

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

此外,如果您不断增加 Rule*,您将永远无法达到过去的 nullptr 值。

我给 Yakk 的答案投了赞成票,因为它是一个看似合理的实现,但很难做到正确。在多态接口中需要考虑和包含很多事情,例如如果您使用== 比较两个RuleBook::iterator 对象,其中一个指向CompanyRules,一个指向BasketballRules,会发生什么情况,多态迭代器的相等性如何工作?

如果将迭代器分配给BasketballRules 对象,将迭代器分配给CompanyRules 对象,会发生什么?您需要多态类型的“深拷贝”。

您需要为每个容器使用不同的派生迭代器实现类型,CompanyRule 容器的迭代器类型需要了解有关 CompanyRule 类型的所有信息,等等每个派生容器类型。这些具体的迭代器实现类型中的每一个都需要将几乎整个迭代器接口实现为虚函数。实现它的难度表明设计存在问题。

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

struct RuleBook
{
  typedef std::vector<Rule*> list_type;
  typedef list_type::iterator iterator;

  virtual iterator begin() = 0;
  virtual iterator end() = 0;
};

struct CompanyRules :
    public RuleBook
{
    CompanyRules() : m_list{ &m_r } { }
    Rule m_r;

    iterator begin() { return m_list.begin(); }
    iterator end() { return m_list.end(); }

private:
    list_type m_list;
};

struct BasketballRules :
    public RuleBook
{
    BaseketballRules() : m_list{ &m_r, m_rp, *m_rpp } { }

    // return the three Rules, one-at-a-time, in succession
    Rule   m_r;
    Rule*  m_rp;
    Rule** m_rpp;

    iterator begin() { return m_list.begin(); }
    iterator end() { return m_list.end(); }

private:
    // N.B.
    // must update m_list[1] any time m_rp changes
    // must update m_list[2] any time the pointee of m_rpp changes (harder!)
    list_type m_list;
};

【讨论】:

  • +1 太棒了 - 用工作代码提供深思熟虑的答案... ...我会尝试一下
【解决方案2】:

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

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


应如何实施篮球规则和公司规则?

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

这些问题与 range-base-for-loop 的工作方式不同。您应该专门针对这些问题提出其他问题。

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

【讨论】:

  • +1 用于您有用的 cmets - 我正在研究您的观点以及 iterators 的构造方式 - 也许我需要在这里编写自己的自定义迭代器。
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

    也许您可以尝试类似 Alex Alllain 在他关于基于范围的 for 循环的教程中解释 here 的方法。它还有一个示例,用于制作可通过基于范围的 for 循环进行迭代的数据结构

    需要的东西是

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

    【讨论】:

      猜你喜欢
      • 2018-03-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-07
      • 1970-01-01
      • 2016-01-23
      • 2014-11-29
      • 2017-07-03
      相关资源
      最近更新 更多