istream_iterator 不进行零初始化

istream_iterator Does Not Zero-Initialize

这是一个Minimal, Complete, Verifiable Example我知道这不是copacetic。无论如何,给定结构:

struct Foo {
    int even;
    int odd;
};

istream& operator>>(istream& lhs, Foo& rhs) {
    int input;

    lhs >> input;

    (input % 2 == 0 ? rhs.even : rhs.odd) = input;

    return lhs;
}

我可以做到以下几点:

stringstream bar("1 2 3 4 5 6 7 8 9 0");

for (const auto& i : vector<Foo>{istream_iterator<Foo>(bar), istream_iterator<Foo>()}) {
    cout << i.even << ' ' << i.odd << endl;
}

但是这给了我结果:

-1215720516 1
2 1
2 3
4 3
4 5
6 5
6 7
8 7
8 9
0 9

零初始化 Foo 我可以写代码:

for(Foo i{}; bar >> i; i = Foo{}) {
    cout << i.even << ' ' << i.odd << endl;
}

这给出了我预期的结果:

0 1
2 0
0 3
4 0
0 5
6 0
0 7
8 0
0 9
0 0

我知道有一个不完全覆盖变量的提取运算符是粗略的。这最初源于我的回答 here and my question ,在我看来,我更自然地期望在读取之间对变量进行零初始化。在任何情况下,是否可以使用 istream_iterator 使得变量在读取之间被零初始化,或者我必须使用 for 循环?

Is it possible to use an istream_iterator such that the variable is zero-initialized between reads?

如果您查看 internals of the istream_iterator 它具有以下内部状态。

private:
  istream_type* _M_stream;
  _Tp       _M_value;
  bool      _M_ok;

其中_M_value是默认构造的。

当使用 ++/++ 时,它会调用 _M_read(),它在其实现中具有以下相关行。

*_M_stream >> _M_value;

因此迭代器本身永远不会触及提取器之外的 Foo 的状态,并且 _M_value 在调用之间重复使用。也就是说,您需要自己以某种方式对其进行初始化。我觉得在operator>>是一个合理的地方。

in my mind had a more natural expectation of zero-initializing the variable in-between reads

这是不正确的期望。 operator>> 应该 完全 单独 负责初始化对象。您不能假设该对象以前是 default/value-initialized。一个非常标准的用例是在 while 循环中读取所有对象:

Foo foo;
while (std::cin >> foo) { ... }

第二次通过时,foo 将具有旧值 - 这里没有任何归零。所以你需要确保当你的运算符returns时,新对象完全由你设置。

最简单的就是先对其进行值初始化:

istream& operator>>(istream& lhs, Foo& rhs) {
    int input;    
    lhs >> input;
    rhs = Foo{}; // <== add this
    input % 2 == 0 ? rhs.even : rhs.odd) = input;
    return lhs;
}

或者您可以手动编写两者:

if (input % 2 == 0) {
    rhs.odd = 0;
    rhs.even = input;
}
else {
    rhs.odd = input;
    rhs.even = 0;
}

或聚合初始化每个案例:

rhs = input % 2 == 0 ? Foo{input, 0} : Foo{0, input};

无论如何,operator>> 负责将您想要清零的值清零。