首页 > 代码库 > C++坑点集合 - 2 严格的Multipass Guarantee
C++坑点集合 - 2 严格的Multipass Guarantee
之前写了一个char32_iterator,简单说就是封装一个string::const_iterator,在operator*的时候将它引用的utf-8序列转为utf-32编码的单个字符返回——这看上去很简单。平时各位在编程的过程中一定会遇到类似的需求:实现一个惰性的transform,在一个容器的每一个元素上应用一个转换函数,但不是立即应用,而是等到使用它的时候即时转换。这听起来就像C#的Linq或者Java的Stream API。熟悉C++的你,一定会想到封装一个迭代器,就像我前面的char32_iterator一样,对引用的原始序列进行一个变换——这个变换还多半是纯函数,在operator*被调用的时候返回这个变换后的值。
思考起来没什么问题,等到编写的时候,你会遇到一个问题,那就是,这个被封装过一层的迭代器——我们以后叫他proxy iterator——应该属于哪种iterator category呢?是继承被封装的迭代器的category吗?如果这个变换函数是纯函数,那么是否说明这个迭代器可以重复扫描整个序列而使返回结果不变,满足Multipass Guarantee从而至少可以是一个Forward Iterator呢?
结果很残酷,这个proxy iterator,不管它封装的迭代器是什么category,它只能是一个Input Iterator,也就是最弱的那个迭代器。为什么呢?因为它不满足Multipass Guarantee。
Multipass Guarantee的定义在这里
简单说,Multipass Guarantee不仅仅要求迭代器允许拷贝和在解引用后原位置仍有效,同时它还要求相同的两个迭代器,解引用后得到的对象应该是同一对象,进一步说,同一对象指的是相同地址。这也就要求,我们的迭代器至少要指向一个确定存在的序列,才能拥有满足Multipass Guarantee的必要条件。像上面所说的proxy iterator,在解引用的时候即时返回变换的结果,是个右值,就算你提前算好保存在了迭代器内部,返回它的引用,也不满足相同迭代器指向同一对象的要求,所以这就不满足Multipass Guarantee。
最后我们的proxy iterator的category不得不回退到了Input Iterator上,最后一个问题是,迭代器的operator*要求返回引用,而我们的迭代器解引用时返回的是右值,这可以吗?可以,cppreference又说了,不是Forward Iterator的Input Iterator,operator*的返回值可以不是引用——可以是右值,或者一个proxy类什么的随你的便——这个洞开的好啊,不然真不知道怎么实现cpplinq。
p.s. 这篇文章翻译腔很重,我也不知道为什么,但是文章确实是我原创的。
C++坑点集合 - 2 严格的Multipass Guarantee