作者回复: 肯定还有更好的 C++ 代码的。学习无止境!
认真学习,应该不用那么久(我还没有极客时间专栏来帮助我学习呢😌)。
反过来,说明老程序员还有点价值么。🤗
作者回复: 现在 r.begin() 和 r.end() 可以是不同类型了。
作者回复: 你的两个疑问实际是针对同一个地方。
auto&& 那句是用一个“万能”引用捕获一个对象,左值和右值都可以。C++ 的生命期延长规则,保证了引用有效期间,istream_line_reader 这个“临时”对象一直存在。没有生命期延长的话,临时对象在当前语句执行结束后即销毁。
作者回复: 好问题。不过,你和getline的版本仔细分析比较一下的话,也是一个string,大家半斤八两。毕竟,按我们的用法,一个流上你也只会用一个有效的istream_line_reader::iterator。另外,遍历的时候我用的是const string&,没有额外的拷贝。
作者回复: 1 对。2 你需要自己实验一下,再想想会不会有其他副作用。
作者回复: 1. 用 ++ 是最合理的,但也有一个奇怪的地方,目前还没人说到。
2. 这个就是后置 ++。迭代器要求前置和后置 ++ 都要定义,虽然我目前只使用了前置版本。
作者回复: 1. 不能写 const,因为你修改了自己。
2. 就算能写也防不了,因为你返回的是个全新的对象。
作者回复: 作为input iterator,本来你就不应该遍历第二次的。这个不是问题。
作者回复: 得到一个空的不能遍历的迭代器。跟任何 end() 相等比较返回真,因而你不可以对它做 ++ 操作。如果你要硬来,它就死给你看。
作者回复: cout << *it 就是读;
*it = 42 就是写。
作者回复: 本身没有任何问题。如何保证行为安全(如异常安全)是个独立问题,跟是否在构造函数里没啥关系。尽量不使用裸指针非常重要,用了的话就需要照顾很多细节了……
作者回复: 从实际使用上讲是这样。单从规定上来讲,输入迭代器不禁止调用begin多次的。所以目前的行为不理想,但一般没问题。
作者回复: 公布第 1 个问题的答案吧:
#include <fstream>
#include <iostream>
#include "istream_line_reader.h"
using namespace std;
int main()
{
ifstream ifs{"test.cpp"};
istream_line_reader reader{ifs};
auto begin = reader.begin();
for (auto it = reader.begin();
it != reader.end(); ++it) {
cout << *it << '\n';
}
}
以上代码,因为 begin 多调用了一次,输出就少了一行……
作者回复: 不是我想的那个……
这个是个问题,但一般不必解决。要能够比较,对性能影响太大。我线上的版本里是有下面这段注释的:
// This implementation basically says, any iterators
// pointing to the same stream are equal. This behaviour
// may seem a little surprising in the beginning, but, in
// reality, it hardly has any consequences, as people
// usually compare an input iterator only to the sentinel
// object. The alternative, using _M_stream->tellg() to
// get the exact position, harms the performance too dearly.
// I do not really have a better choice.
//
// If you do need to compare valid iterators, consider using
// file_line_reader or mmap_line_reader.
作者回复: #1 对我来讲,这不是意外。就像你对空指针解引用崩溃也不是意外一样。没有有效的 istream,你要取这个流的开头,出错很正常。
#2 因为你没有看到我想的问题,所以第二部分也不是我要的回答……
作者回复: 前面部分没有问题。后面的失败部分,没看懂你的意思。
我这个例子重点在于,null_sentinel 表示的不是一个位置,而是一个条件。我们可以用迭代器来表示一个条件,这是对它的功能的很大扩展。虽然这种扩展方式性能非常好,但这个功能主要不是优化,而是新的可能性。
作者回复: 拷贝构造失败的话,直接抛异常了,当然不会继续读取下一行。
作者回复: 给个例子你仔细研读一下吧。功能是遍历字符串,直到遇到字符串结尾(事先不知道字符串长度)。
#include <stdio.h>
struct null_sentinel {};
bool operator!=(const char* ptr, null_sentinel)
{
return *ptr != 0;
}
// operator!=(null_sentinel, const char* ptr), operator==, ...
struct c_string_view {
c_string_view(const char* str) : str_(str) {}
const char* begin() const { return str_; }
null_sentinel end() const { return null_sentinel{}; }
const char* str_;
};
int main()
{
c_string_view msg{"Hello world!"};
for (char ch : msg) {
putchar(ch);
}
putchar('\n');
}
作者回复: 你对容器的 end() 解引用,同样可能崩溃(取决于实现)。你不被允许这么做。这么做,你就进入了 undefined behavior 的领域,系统是死还是出 bug 都正常。
作者回复: 你说的情况会出问题,但这个是需要调用者保证的,我做不了什么事情。
再想想。🤓