【发布时间】:2018-09-04 08:56:54
【问题描述】:
我觉得这个问题一定被问过并解决了很多次,因为在我看来这似乎是一个很笼统的场景,但我找不到任何可以让我找到解决方案的方向。
我正在尝试实现一个通用的可迭代 Generator 对象,该对象会产生一个数字序列,直到满足某个终止条件,表示已达到该条件以停止迭代。
本质上,基本思想是拥有类似于 Python 的生成器的东西,其中一个对象产生值直到它没有更多的产生,然后引发 StopIteration 异常以通知外部循环序列已完成。
据我了解,问题分为创建序列生成对象,然后在其上获取迭代器。
对于生成序列的对象,我想我会定义一个基本的 Generator 类,然后扩展它以提供特定的行为(例如,从一组范围或固定值列表中获取值等)。所有Generaors 都会在每次调用operator() 时生成一个新值,或者如果生成器运行到序列末尾,则抛出ValuesFinishedException。
我是这样实现的(我以单范围子类为例,但我需要能够对更多类型的序列进行建模):
struct ValuesFinishedException : public std::exception { };
template <typename T>
class Generator
{
public:
Generator() { };
~Generator() { };
virtual T operator()() = 0; // return the new number or raise a ValuesFinishedException
};
template <typename T>
class RangeGenerator : public Generator<T>
{
private:
T m_start;
T m_stop;
T m_step;
T m_next_val;
public:
RangeGenerator(T start, T stop, T step) :
m_start(start),
m_stop(stop),
m_step(step),
m_next_val(start)
{ }
T operator()() override
{
if (m_next_val >= m_stop)
throw ValuesFinishedException();
T retval = m_next_val;
m_next_val += m_step;
return retval;
}
void setStep(T step) { m_step = step; }
T step() { return m_step; }
};
但是,对于迭代器部分,我被卡住了。
我研究了我能想到的“迭代器”、“生成器”和同义词的任何组合,但我发现的只是考虑了生成器函数具有无限数量的值的情况(例如参见boost's generator_iterator)。我曾想过自己编写一个Generator::iterator 类,但我只找到了end 定义明确的琐碎迭代器(链表、数组重新实现)的示例。提前不知道什么时候结束,只知道如果我正在迭代的生成器引发异常,我需要将迭代器的当前值设置为“end()”,但我没有知道如何表示它。
编辑:添加预期的用例
这个类的原因是有一个可以循环的灵活序列对象:
RangeGenerator gen(0.25f, 95.3f, 1.2f);
for(auto v : gen)
{
// do something with v
}
范围的例子只是最简单的例子。我将至少有三个实际用例:
- 简单范围(可变步长)
- 多个范围的串联
- 存储在向量中的常量值序列
对于其中的每一个,我都计划创建一个 Generator 子类,并为抽象 Generator 定义迭代器。
【问题讨论】:
-
只为您的迭代器设置一个标记状态,表示“结束”,并编写您的比较运算符,以便
i == end只有在i达到其终端条件时才为真。这可以像添加bool isAtEnd成员一样简单。不要使用异常,将 Python 约定导入 C++ -
是的,我觉得使用异常是不好的做法,但是来自 Python 的我选择了我知道的解决方案。问题是,IMO 异常清楚地将“序列中还有另一个值”的情况与“序列结束”分开。我将如何通知调用
generator()的人该实例实际上无一例外地运行了整个序列?我不能假设我总是有一个值要返回......(写这个我想std::optional<T>作为operator()的返回值......会更好吗?) -
我认为您试图对 Python 建模过于紧密,这就是为什么您现在无法将 Python 生成器与 C++ 迭代器匹配。 C++ 中的迭代器在异常之前不会调用,您可以比较它们。您的“结束”迭代器是
m_next_val == m_stop的迭代器。您还需要编写一个相等方法来检查这一点。 -
如果你想生成无限序列,你需要迭代器接口吗?当你得到一个迭代器时,你接下来会用它做什么? (即用例)
-
@Hitobat 与其他语言相同,例如:en.cppreference.com/w/cpp/algorithm/copy_n