如何实现符合标准的迭代器/容器
How to implement an iterator/container that is standards-compliant?
假设我有一个名为RuleBook
的抽象容器类。RuleBook
的用户希望能够对RuleBook
进行前向迭代以获得Rule
。
与标准容器不同,此处对具体子类的内存布局没有限制。 相反,子类的实现者需要遵守RuleBook
的前向迭代要求,并根据其自身的数据结构满足此要求。
我认为RuleBook
应该包含纯虚拟begin()
和end()
,以便它可以与基于范围的 for一起使用,但我遇到了一些问题。
begin() 和 end() 的签名应该是什么? 篮球规则和公司规则应该如何实施?
当迭代器经过最后一项时,我们如何处理结束条件?
在下面的示例中,您可以假设m_rp
和m_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[] )
{
}
这很难做到正确。
begin() 和 end() 的签名应该是什么?
没有太多选择,他们几乎必须是类似的东西
RuleBook::iterator begin();
RuleBook::iterator end();
(如果需要,可以添加const
重载)
篮球规则和公司规则应该如何实施?
仔细:)
当迭代器经过最后一项时,我们如何处理结束条件?
您可以正确设计迭代器类型,使其正常工作。 您需要一个可以比较相等性并且可以递增的迭代器类型。当您有一个迭代器到容器中的最后一个项目并递增它时,它必须等于过去结束的迭代器。
如果我使用 Rule* 作为我的迭代器,null_ptr作为我的超越端点来实现整个事情,这是否符合 STL 标准?
不。 如果您的迭代器类型只是Rule*
那么递增迭代器不会移动到下一个Rule
,它只是指向内存中的下一个位置,该位置甚至可能不是Rule
对象,从而导致未定义的行为。 例如,对于BasketballRules
,如果您有指向m_r
的Rule*
并且您递增它,那么您不是指向一个有效的Rule
对象,而是指向m_rp
占用的内存,即Rule*
并取消引用它是未定义的行为。
此外,如果您不断递增Rule*
则永远不会达到过去的结束nullptr
值。
我给Yakk的回答投了赞成票,因为它是一个合理的实现,但很难做到正确。 多态接口中需要考虑和包含很多事情,例如,如果您使用==
比较两个RuleBook::iterator
对象,其中一个指向CompanyRules
,一个指向BasketballRules
,会发生什么,相等性如何适用于多态迭代器?
如果将迭代器分配给BasketballRules
对象,将迭代器分配给CompanyRules
对象,会发生什么情况?您需要多态类型的"深拷贝"。
每个容器都需要不同的派生迭代器-impl 类型,CompanyRule
容器的迭代器类型需要了解有关CompanyRule
类型的所有信息,依此类推每个派生容器类型。这些具体的迭代器-impl 类型中的每一个都需要将几乎整个迭代器接口实现为虚函数。 实现它的难度表明设计存在问题。
更简单的设计是针对每个派生容器类型管理相同类型的实际物理容器。 特定于每个派生容器的代码只包括每当派生对象的内容更改时更新列表的内容。然后,迭代器类型是简单和非多态的,例如
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;
};
确切的签名无关紧要,因为基于范围的 for 循环是根据所使用的表达式定义的。如果找到begin
或end
成员函数,则像__range.begin()
和__range.end()
一样调用它们。签名无关紧要的一个例子是,这些成员函数可以具有任意数量和类型的参数,只要它们可以像.begin()
和.end()
一样被调用(这意味着参数必须具有默认值)。
如果类型没有begin
和end
成员函数,则使用表达式begin(__range)
和end(__range)
。(其中__range
auto &&__range
使用您在 range-for-loop 中使用的表达式进行初始化)。因此,只要参数相关查找有效,确切的签名就很重要。
篮球规则和公司规则应该如何实施?
当迭代器经过最后一项时,我们如何处理结束条件?
这些是与范围基础循环如何工作不同的问题。您应该专门询问有关这些的其他问题。
但是,如果您使用指针作为迭代器,那么使用nullptr
作为结束迭代器是不合适的,因为增加最后一个有效指针不会给你一个空指针;它会给你一个超过范围末尾的指针。此外,如果您使用Rule*
作为迭代器,那么您不会将实现留给容器类;容器必须维护一个连续的规则数组。
Boost 有一些迭代器帮助程序模板类 - crtp,需要你实现一些方法。 基于pImpl
的用户将允许迭代器的合规和虚拟行为。 也就是说,pImpl
是一个纯粹的虚拟抽象类,迭代器将其工作委托给它。
编写一个纯虚拟pImpl
迭代器。 这就是返回类型begin
和end
. 子类使用具体的pImpl
实例创建迭代器的实例,这些实例是为存储数据的方式自定义编写的。
也许你可以尝试像Alex Allain在他的教程中解释的关于基于范围的for循环一样。它还有一个示例,用于创建可通过基于范围的 for 循环迭代的数据结构
需要的东西是
- 一个 .begin() 和一个 .end() 方法。它们也可以是独立的。
- 运算符 != 、++ 和 * 重载,以便支持需要执行的迭代器操作。
- 为什么我们在C++标准中没有"const 迭代器",而是const_iterator?
- 对于C字符串,是否有一个标准的C++迭代器
- 标准上的 OMP 和并行操作::set<...>::迭代器
- 迭代器的标准接口::operator*返回的比T&和std::p air更多
- 将字符串::迭代器转换为标准::字符串
- 为什么标准容器迭代器不会重载"->*"?
- 为什么反向迭代器不是 C++17 标准下的正式迭代器类别?
- 如何使用标准迭代器使用`boost :: range`迭代器
- 如何在课堂上实现标准迭代器
- 如何计算标准::矢量::<int>迭代器和标准::矢量<int>::reverse_iterator之间的距离?
- 如何实现符合标准的迭代器/容器
- 模板标准::映射::迭代器实例化
- 标准库容器迭代器的递增/递减迭代器是否具有确定性
- 推进标准映射的迭代器
- 如何检查是否可以推进简单C++标准库迭代器
- 具有子类容器的子类的非标准迭代器
- 标准迭代器分配错误
- C++模板,使用标准迭代器的错误
- C++标准库是否禁止非标准迭代器类别
- 是否允许抛出标准迭代器操作