Ok to use std::getline() with a moved-from std::st

2020-08-24 12:23发布

问题:

Is it safe and well-defined for the second argument to std::getline(std::istream&, std::string&) to be an lvalue referring to a moved-from std::string, and, if so, is that string restored from its moved-from state, so methods such as pop_back() can be safely invoked?

Put more simply, does writing to a string with getline() have equivalent semantics to assigning to that string?

Or more concretely, is the following (somewhat contrived) snippet well-defined and correct?

std::ifstream f("foo.txt");

std::vector<std::string> lines;

for (std::string s; std::getline(f, s); lines.push_back(std::move(s)))
        if (!s.empty() && s.back() == '\r')
                s.pop_back();

Optimized (-march=native -O3) builds of this snippet with g++ and clang++ appear to work as expected, but that is of course no guarantee.

I'd like to know if this is relying only on well-defined behavior according to the semantics of getline() in the C++11 standard, or, if not, if it's well-defined by a later edition of the standard, or, if not, if it's at least explicitly defined by any/all of the major implementations (G++, Clang++, Visual C++, Intel C++ Compiler).

NB: This is not a duplicate of previous questions asking whether it's safe to assign to a moved-from object (yes, if it's a trivial or STL type) because getline() is not the assignment operator.

回答1:

Your code is safe, but only because you are checking if the getline successfully stored something in s. If the sentry internal to getline failed to initialize, then nothing would be assigned to s and it would be left in the valid-but-unspecified state. As long as getline succeeds in setting s to a known state, you're good.

And the very first thing getline does after successful sentry construction is set s to a zero size (a known state).

More details below in GManNickG's comment.



回答2:

Is it safe and well-defined for the second argument to std::getline(std::istream&, std::string&) to be an lvalue referring to a moved-from std::string

Yes. Although it the moved-from string is left unspecified by the standard according to [string.cons]p2:

[...]. In the second form, str is left in a valid state with an unspecified value.

The string is still in a valid state, it is just unspecified, i.e. you can't know which state it is in (in may be filled with some random characters). You can still to pass it to std::getline, because the standard says in [string.io]p1 that str.erase() is called on the string, which empties it:

[...] calls str.erase() [...]

(if the stream is valid, but I'll assume that is in your case).

No matter what the unspecified state of the string is, it is emptied at the start of std::getline, so it doesn't matter what state it is in.



回答3:

A move assignment or move c'tor should always leave the moved object in a valid state.

In case of a std::string, when you std::move it, it stays a valid but unspecified string. From libc++'s source:

// __str is a valid, but unspecified string. 
basic_string(basic_string&& __str) noexcept
  : _M_dataplus(_M_local_data(), std::move(__str._M_get_allocator()))

And since getline() only appends chars to the string, as long as it succeeds, the output will be as expected. From cplusplus.com:

Note that any content in str before the call is replaced by the newly extracted sequence.

Each extracted character is appended to the string as if its member push_back was called.