side effects of moving standard types

207 views
Skip to first unread message

Nathan Ernst

unread,
Apr 16, 2015, 9:02:36 PM4/16/15
to std-dis...@isocpp.org
(This question is based upon document ISO/IEC 14882:2011(E))

I'm writing a parser that's going to end up generating lots of instances of standard strings and vectors, and I'm trying to be as unobtrusive in terms of memory as I can.  I'm trying to move the generated strings and vectors for obvious reasons, but it got me thinking about the state of the the carcass of the moved instance.

For strings, I found the language in § 21.4.2 paragraph 2 that "In the second form, str is left in a valid state with an unspecified value." The second form refers to the the rvalue-reference constructor of basic_string.

From this language, I expect I should be able to call methods on a previously moved instance without introducing undefined behavior, but the result of those calls are unspecified (calling c_str could return an empty string or anything else). What I gather from this is that if I wanted to reuse a previously moved string instance, I would need re-initialize it in order to get to a known state.

As an example, if I wrote:

std::function<void(std::string&&)> sink = ...;
std::string s;

for (int i = 0; i < 26; ++i)
{
 s.append('a' + i);
 sink(std::move(s));
}

It would be conformant for sink to receive "a", "b", "c".... or to receive "a", "ab", "abc", or really anything else after initially receiving "a".

Question boils down to: To be in a known & valid state, do I need to reinitialize a moved type in order to be able to guarantee behavior?

i.e. Do I need to do the following in order to ensure sink receives "a", "b", "c"...:

std::function<void(std::string&&)> sink = ...;
std::string s;

for (int i = 0; i < 26; ++i)
{
 s.append('a' + i);
 sink(std::move(s));
 s = std::string();  // possibly redundant?
}

In general would this assumption hold across all standard movable types? Is the extra re-initialization necessary to ensure cross platform/implementation behavior?

Thanks,
Nate

Richard Smith

unread,
Apr 16, 2015, 9:18:01 PM4/16/15
to std-dis...@isocpp.org
On Thu, Apr 16, 2015 at 6:02 PM, Nathan Ernst <nathan...@gmail.com> wrote:
(This question is based upon document ISO/IEC 14882:2011(E))

I'm writing a parser that's going to end up generating lots of instances of standard strings and vectors, and I'm trying to be as unobtrusive in terms of memory as I can.  I'm trying to move the generated strings and vectors for obvious reasons, but it got me thinking about the state of the the carcass of the moved instance.

For strings, I found the language in § 21.4.2 paragraph 2 that "In the second form, str is left in a valid state with an unspecified value." The second form refers to the the rvalue-reference constructor of basic_string.

From this language, I expect I should be able to call methods on a previously moved instance without introducing undefined behavior, but the result of those calls are unspecified (calling c_str could return an empty string or anything else). What I gather from this is that if I wanted to reuse a previously moved string instance, I would need re-initialize it in order to get to a known state.

As an example, if I wrote:

std::function<void(std::string&&)> sink = ...;
std::string s;

for (int i = 0; i < 26; ++i)
{
 s.append('a' + i);
 sink(std::move(s));
}

It would be conformant for sink to receive "a", "b", "c".... or to receive "a", "ab", "abc", or really anything else after initially receiving "a".

Question boils down to: To be in a known & valid state, do I need to reinitialize a moved type in order to be able to guarantee behavior?

"reinitialize" isn't the best word (because you would not do this via initialization), but yes, you need to set the value of the object (through assignment, x.clear(), or similar).

i.e. Do I need to do the following in order to ensure sink receives "a", "b", "c"...:

std::function<void(std::string&&)> sink = ...;
std::string s;

for (int i = 0; i < 26; ++i)
{
 s.append('a' + i);
 sink(std::move(s));
 s = std::string();  // possibly redundant?
}

In general would this assumption hold across all standard movable types?

Yes, except for library types that guarantee that a moved-from object has a specific value (a notable exception is unique_ptr, which is guaranteed to be null when it has been moved from).
 
Is the extra re-initialization necessary to ensure cross platform/implementation behavior?

Yes. For instance, some implementations use a small string optimization for std::string, and it is common for moving from a string in its "small" state to leave the original string's value alone, which could result in calling your sink with arguments "a", "ab", "abc", "abcd", ..., "abcdefgh", "i", "j", ... for instance (for an 8 byte SSO buffer).

Nathan Ernst

unread,
Apr 16, 2015, 9:50:02 PM4/16/15
to std-dis...@isocpp.org
Got it, Richard. Appreciate the response, and, unfortunately, confirming my fears.  The small string example really helped to make it clear why.

Regards,
Nate

--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.

Nevin Liber

unread,
Apr 16, 2015, 9:53:40 PM4/16/15
to std-dis...@isocpp.org
On 16 April 2015 at 20:50, Nathan Ernst <nathan...@gmail.com> wrote:
Got it, Richard. Appreciate the response, and, unfortunately, confirming my fears.  The small string example really helped to make it clear why.

If you are that concerned, you can always create your own string type that wraps std::string with a move constructor / assignment that clear()s the source afterwards.
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404

Nathan Ernst

unread,
Apr 16, 2015, 10:17:37 PM4/16/15
to std-dis...@isocpp.org

'Fear' was probably a too strong and wrong word to use, yet Richard's explanation confirmed that I shouldn't  rely upon observed  behavior of one compiler.

--

gmis...@gmail.com

unread,
Apr 21, 2015, 9:44:07 PM4/21/15
to std-dis...@isocpp.org
So I'm absolutely clear on this subject, on my machine this program:

#include <string>
#include <cassert>
#include <cstdio>
void f(std::string&& s);
int main()
{
 std::string s = "hello";
 f(std::move(s));
 std::printf("main() s: \"%s\"\n", s.c_str());
 std::printf("main() empty: %s\n", s.empty() ? "true" : "false");
}
void f(std::string&& s)
{
 std::string s2 = std::move(s);

// on Windows with clang and libc++, the output is:
main() s: ""
main() empty: true

Are you saying that this output is undefined, i.e. that on another machine the output might be something different?
Such as:
main() s: "hello"
main() empty: false

Nevin Liber

unread,
Apr 21, 2015, 9:52:41 PM4/21/15
to std-dis...@isocpp.org

On 21 April 2015 at 20:44, <gmis...@gmail.com> wrote:
Are you saying that this output is undefined, i.e. that on another machine the output might be something different?

Yes.  Not quite undefined, but in a valid but unspecified state.

From n4431 [defns.valid]:
valid but unspecified state

an object state that is not specified except that the object’s invariants are met and operations on the object behave as specified for its type

[ Example: If an object x of type std::vector<int> is in a valid but unspecified state, x.empty() can be called unconditionally, and x.front() can be called only if x.empty() returns false. —end example ]

gmis...@gmail.com

unread,
Apr 22, 2015, 12:16:18 AM4/22/15
to std-dis...@isocpp.org
I have seen a lot of code like that, which is technically broken then. I've written code like that myself regarding string. 
I think perhaps the standard should change to make such code work as expected for things like string.

e.g.: guarantee this assert will never fail.

std::string s1 = "hello";
std::string s2 = std::move(s)
assert(s1.empty());

What says the people?

Francis (Grizzly) Smit

unread,
Apr 22, 2015, 12:21:00 AM4/22/15
to std-dis...@isocpp.org
what make the compiler deduce that s is s1 ??? not a good idea if it's even doable



--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.

--
   .~.     In my life God comes first....
   /V\         but Linux is pretty high after that :-D
  /( )\    Francis (Grizzly) Smit
  ^^-^^    http://www.smit.id.au/
-----BEGIN GEEK CODE BLOCK-----
Version: 3.1
GM/CS/H/P/S/IT/L d- s+:+ a++ C++++ UL++++$ P++ L+++$ E--- W++
N W--- M-- V-- PE- PGP t+ 5-- X-- R- tv b++++ D-
G e++ h+ y?
------END GEEK CODE BLOCK------
http://www.geekcode.com/

David Krauss

unread,
Apr 22, 2015, 12:22:00 AM4/22/15
to std-dis...@isocpp.org
On 2015–04–22, at 9:51 AM, Nevin Liber <ne...@eviloverlord.com> wrote:


On 21 April 2015 at 20:44, <gmis...@gmail.com> wrote:
Are you saying that this output is undefined, i.e. that on another machine the output might be something different?

Yes.  Not quite undefined, but in a valid but unspecified state.

More specifically, std::string may implement the small string optimization.

On 2015–04–22, at 12:16 PM, gmis...@gmail.com wrote:

the standard should change to make such code work as expected for things like string.

e.g.: guarantee this assert will never fail.

std::string s1 = "hello";
std::string s2 = std::move(s)
assert(s1.empty());

For std::vector (over std::allocator), as far as I know, the moved-from state is constrained to be empty by the requirement that iterators to the moved-from object remain valid, pointing to the moved-to object.

Nevin Liber

unread,
Apr 22, 2015, 1:56:14 AM4/22/15
to std-dis...@isocpp.org
On 21 April 2015 at 23:21, David Krauss <pot...@gmail.com> wrote:
For std::vector (over std::allocator), as far as I know, the moved-from state is constrained to be empty by the requirement that iterators to the moved-from object remain valid, pointing to the moved-to object.

That may be true, which means we probably should change the note (even though it is non-normative) with a better example. 

David Rodríguez Ibeas

unread,
Apr 22, 2015, 3:00:50 AM4/22/15
to std-dis...@isocpp.org
On Wed, Apr 22, 2015 at 5:16 AM, <gmis...@gmail.com> wrote:
I have seen a lot of code like that, which is technically broken then. I've written code like that myself regarding string. 
I think perhaps the standard should change to make such code work as expected for things like string.

I have seen a lot of people expecting wrong programs to behave correctly, that does not mean that the language has to adjust to match the expectations of those people, rather that those people need to learn what can and cannot be done within the bounds of the language.

People expects arrays and pointers to be the same, believe that dereferencing null is fine as long as you immediately take the address of the lvalue or a member of it, you can do network-to-host of a double in place through reinterpret_cast<int64_t*> and ntohll... should all those be adjusted to the expectations of users?

Expecting that the resulting string is empty might not be a big change, but the moved-from object would have to undertake two modifications that are not needed today: reset the size to 0, set the first character in the buffer to 0.  Neither of those operations help the cause of moving from a real rvalue and feel a bit like setting a pointer to nullptr after deleting the object in the destructor, not really that useful.  If you really want the string to be cleared you have the option of paying for the operation yourself, without forcing all other uses onto that cost.

    David

gmis...@gmail.com

unread,
Apr 22, 2015, 7:13:20 AM4/22/15
to std-dis...@isocpp.org, dib...@ieee.org
You are exactly correct about what I think the "problem" is and what I think the possible "solution" might be.
Most implementations (it appears) already conform to the design that I find least surprising and that's probably exactly why.
i.e. moving from a string leaves it empty.
But if we are saying code shouldn't rely on that, then I think we need compilers to diagnose our wrong assumptions here and/or have some major implementations disagree, so we are less likely to rely on non guaranteed behaviour.
Or we need to make the practice standard so they can rely on it.
If we do nothing, I think eventually there will be a large body of code that needs support and we'll standardize it anyway.
I don't think we should sleep walk in that which I think we will do if stick to the status quo.

Howard Hinnant

unread,
Apr 22, 2015, 10:45:19 AM4/22/15
to std-dis...@isocpp.org
Assuming you meant:

std::string s2 = std::move(s1);

———

Questions:

1. Is this guarantee intended only for std::string, and not other instantiations of std::basic_string?

2. Is this guarantee intended only for the move constructor, or is it also intended for the move assignment operator? Or what about other operations within the std::lib that take a std::string&& (or std::basic_string<…>&&) such as vector<string>::insert(const_iterator, string&&)?

Howard

Bo Persson

unread,
Apr 22, 2015, 10:46:50 AM4/22/15
to std-dis...@isocpp.org
On 2015-04-22 13:13, gmis...@gmail.com wrote:
>
>
> On Wednesday, April 22, 2015 at 7:00:50 PM UTC+12, David Rodríguez Ibeas
> wrote:
>
>
>
> On Wed, Apr 22, 2015 at 5:16 AM, <gmis...@gmail.com <javascript:>>
It's not "most implementations" (and there aren't that many). In other
implementations, using the short-string-optimization, moving from a long
string might leave it empty, but moving from a short string might be
implemented as a copy (because that happens to be more efficient).

When the basic idea about moving values is that it should be a speed
optimization, some people will not like the compiler adding extra code
just-in-case.

Isn't it better that everyone learns that a moved-from object is valid,
but with an unknown value? If you need it to have a specific value, you
just set that value - like assigning or moving another value into the
string.


Bo Persson




Nevin Liber

unread,
Apr 22, 2015, 11:00:26 AM4/22/15
to std-dis...@isocpp.org
On 22 April 2015 at 06:13, <gmis...@gmail.com> wrote:


You are exactly correct about what I think the "problem" is and what I think the possible "solution" might be.
Most implementations (it appears) already conform to the design that I find least surprising and that's probably exactly why.

Do those implementations even conform to C++11?  For instance, the string implementation in gcc won't until gcc5.
 
But if we are saying code shouldn't rely on that, then I think we need compilers to diagnose our wrong assumptions here and/or have some major implementations disagree, so we are less likely to rely on non guaranteed behaviour.

Talk to your implementation vendor.
 
If we do nothing, I think eventually there will be a large body of code that needs support and we'll standardize it anyway.

The intent is that the moved-from string is either destroyed or replaced using assignment.  Anything else is a micro optimization that most people really shouldn't be doing.  Just sprinkling std::move all over the place makes code harder to understand, and in some cases, is even a pessimization (such as when interfering with RVO).

And what about calls which only conditionally do a move, such as set<T>::insert(T&&)?

gmis...@gmail.com

unread,
Apr 22, 2015, 6:09:34 PM4/22/15
to std-dis...@isocpp.org
Hi Howard

It's also not practical for me to answer your questions explicitly because I suspect the answer requires a detailed analysis of not just string but other types like vector etc. too.

But I am not a strong advocate that a guarantee should be given at all.

The example was to illustrate my concern that if we carry on as we are today, we may have to guarantee what the output of my trivial example is, because so much code like it will exist that depends on what we are already doing today; and that we may already have reached that point.

To my mind, the safest, fastest and easiest thing to teach is the current situation: that a moved from object should not be used at all. But the problem with that rule that it's not natural. That doesn't make the rule wrong because it is simple to teach, but it's still not natural because to my mind it's not what we first assume. It's just way too easy to move from a string and then expect the moved from string to be empty and re-useable and that belief is strengthened by the fact that in all the compilers and libraries I tried my example on that, the string was empty in practice.

I built my example (sorry for the typo by the way) with libc++ and clang but also with g++ 4.9 with the mingw libraries and with msvc. In all cases the string was left empty. I haven't looked at the code of these libraries to see if they contain instructions that encourage reusability, but I suspect some do. But I also suspect that in some cases the instructions are just a by-product of what a class needs to do for it's own sanity on destruction.

The worry though is that despite these results, we are still saying the code in my example is unreliable. Yet no compiler I tried gave me any warning about using a moved from type and because of the other things I've said it might not even be trivial or even possible to do so.

There may be inherent performance tensions between reusing an object and not reusing one too in many circumstances.

So what I'm saying is this all feels like it's something that we should be concerned about and can we improve the situation?

I think what we are doing today through insufficient compiler warnings or possibly assisting libraries is building up a stock of errant code that if we don't do something about it soon, it may mean having to give guarantees we don't like for various reasons. I am sure there must be some precedent in the past where that has had to be done too.

I would be nice if compilers could put more effort into saying "hey, this type may have been moved from, so don't use it further" but is that viable. But without further attention focused in this space, be it by books, compiler warnings or library changes or attributes, I worry that moved from objects might be a new major source of bugs in C++ code going forward.

Don't get me wrong, I think move is awesome, but can we do more to manage it's impact better?
Maybe we designate a [[safe_after_move]] class or function attribute or something and then have the compiler attempt to warn on use of any other method after a move has been seen. It might not be bullet proof but maybe it might help.

Nevin Liber

unread,
Apr 22, 2015, 6:28:51 PM4/22/15
to std-dis...@isocpp.org
On 22 April 2015 at 17:09, <gmis...@gmail.com> wrote:

Don't get me wrong, I think move is awesome, but can we do more to manage it's impact better?
Maybe we designate a [[safe_after_move]] class or function attribute or something and then have the compiler attempt to warn on use of any other method after a move has been seen. It might not be bullet proof but maybe it might help.

The attribute doesn't help.  Someone has to spend the effort to make a thoroughly checking standard library (such as Microsoft did with things like checked iterators).  That's a vendor issue, not a standards issue.

gmis...@gmail.com

unread,
Apr 22, 2015, 6:29:19 PM4/22/15
to std-dis...@isocpp.org
Hi Nevin

I think my reply to Howard might respond to some of your thoughts. My concern is that move opens up a lot of things whereby people are doing things that they really shouldn't be doing. I'm just trying to find ways where we turn more of those things into completely legal things or definitely errant things so that the compiler knows that.

Your conditional move function is interesting because even if certain functions are "safe" to call in a sense after being moved from, what happens is still dependent potentially on if an earlier move did or didn't actually happen and not being aware of that state is where one can easily introduce bugs.

In answer to your question about C++11 conformance. I don't know but I suspect the conformance level won't change the result of my earlier example or peoples opinions on the cross platform reliability of it's output. People are already saying it's not reliable and it will worry me if we can't depend on it or can't diagnose it more.

Howard Hinnant

unread,
Apr 22, 2015, 8:01:54 PM4/22/15
to std-dis...@isocpp.org
On Apr 22, 2015, at 6:09 PM, gmis...@gmail.com wrote:
>
> On Thursday, April 23, 2015 at 2:45:19 AM UTC+12, Howard Hinnant wrote:
> ———
>
>> Questions:
>>
>> 1. Is this guarantee intended only for std::string, and not other instantiations of std::basic_string?
>>
>> 2. Is this guarantee intended only for the move constructor, or is it also intended for the move assignment operator? Or what about other operations within the std::lib that take a std::string&& (or std::basic_string<…>&&) such as vector<string>::insert(const_iterator, string&&)?
>>
>
> It's also not practical for me to answer your questions explicitly because I suspect the answer requires a detailed analysis of not just string but other types like vector etc. too.

It is difficult to analyze a proposal without knowing exactly what is being proposed.

>
> Maybe we designate a [[safe_after_move]] class or function attribute or something and then have the compiler attempt to warn on use of any other method after a move has been seen. It might not be bullet proof but maybe it might help.

Perhaps it would be instructive to recast the question in terms we’re familiar with from C++98. What should this program output?

#include <iostream>
#include <algorithm>
#include <string>

int
main()
{
std::string array[] = {"a", "b", "c"};
const std::size_t sz = sizeof(array)/sizeof(array[0]);
std::remove(array, array+sz, "a");
std::cout << array[sz-1] << '\n';
}

What operations would it be safe to do with the value array[sz-1] after std::remove is called? Is the answer to that question different between C++98, C++03, C++11 and C++14?

Is their any practical difference between array[sz-1] and a C++11 moved-from std::string? Is the issue with std::remove one that has needed (and continues to need) to be solved (since C++98)?

If we were to put the call to std::remove under a try/catch, in the catch clause what operations would be safe to perform on the elements of array?

I guess what I’m getting around to is: Valid but unspecified states are not a new thing with C++11. We have lived with them everywhere we have basic exception safety, and in a few more places such as std::remove, std::remove_if and std::unique. If moved-from std::strings need fixing, don’t we also need to address these other areas as well for consistency? Or is move that special?

Howard

gmis...@gmail.com

unread,
Apr 22, 2015, 8:48:30 PM4/22/15
to std-dis...@isocpp.org


On Thursday, April 23, 2015 at 12:01:54 PM UTC+12, Howard Hinnant wrote:
On Apr 22, 2015, at 6:09 PM, gmis...@gmail.com wrote:
>
> On Thursday, April 23, 2015 at 2:45:19 AM UTC+12, Howard Hinnant wrote:
> ———
>
>> Questions:
>>
>> 1. Is this guarantee intended only for std::string, and not other instantiations of std::basic_string?
>>
>> 2. Is this guarantee intended only for the move constructor, or is it also intended for the move assignment operator?  Or what about other operations within the std::lib that take a std::string&& (or std::basic_string<…>&&) such as vector<string>::insert(const_iterator, string&&)?
>>
>
> It's also not practical for me to answer your questions explicitly because I suspect the answer requires a detailed analysis of not just string but other types like vector etc. too.

It is difficult to analyze a proposal without knowing exactly what is being proposed.

It is. But I didn't propose anything because of what your later question acknowledges. You say "If moved-from std::strings need fixing, don’t we also need to address these other areas as well for consistency?".

And the answer to that is "yes, it might.", but to answer in any more detail is to go list all those other types and get everyone to agree with that and I feel that's to big of a task for me. It's also not something I was strongly advocating. I was looking as much to the other direction in my post as that one.


>
> Maybe we designate a [[safe_after_move]] class or function attribute or something and then have the compiler attempt to warn on use of any other method after a move has been seen. It might not be bullet proof but maybe it might help.

Perhaps it would be instructive to recast the question in terms we’re familiar with from C++98.  What should this program output?

#include <iostream>
#include <algorithm>
#include <string>

int
main()
{
    std::string array[] = {"a", "b", "c"};
    const std::size_t sz = sizeof(array)/sizeof(array[0]);
    std::remove(array, array+sz, "a");
    std::cout << array[sz-1] << '\n';
}

What operations would it be safe to do with the value array[sz-1] after std::remove is called?  Is the answer to that question different between C++98, C++03, C++11 and C++14?

Is their any practical difference between array[sz-1] and a C++11 moved-from std::string?  Is the issue with std::remove one that has needed (and continues to need) to be solved (since C++98)?

If we were to put the call to std::remove under a try/catch, in the catch clause what operations would be safe to perform on the elements of array?

I guess what I’m getting around to is:  Valid but unspecified states are not a new thing with C++11. We have lived with them everywhere we have basic exception safety, and in a few more places such as std::remove, std::remove_if and std::unique.  If moved-from std::strings need fixing, don’t we also need to address these other areas as well for consistency?  Or is move that special?

Howard


I think that move is special in that firstly, it is a core operation that pretty much applies to most types. And secondly, once applied, it obliterates the object pretty profoundly.

None of this is to say that the design of move is wrong.

But what bothers me is that even for my simple example, the result is that apparently (listening to the replies already in) can't guarantee even that code will reliably output the same thing on different platforms AND that no compiler I have seen currently diagnoses the issue. It may not even be possible. That's what worries me.

Your earlier question acknowledges the kinds of things that need to be answered which ever road you go down and the complexity of answering that which is why I haven't tried.

All I'm doing is just laying out what I see as the current state and saying they seem less than ideal and asking what can we do about it. I want the compiler to be able to spot errant use after move as much as we spot errant use after delete and/or to make sensible use after move less platform specific. There may be no perfect here. But better might be obtainable. I like move I just want to make using it less of a source of bugs and am interested in ways of achieving that if it's possible.

Howard Hinnant

unread,
Apr 22, 2015, 9:09:40 PM4/22/15
to std-dis...@isocpp.org
On Apr 22, 2015, at 8:48 PM, gmis...@gmail.com wrote:
>
> And secondly, once applied, it obliterates the object pretty profoundly.

This is a notion I’d prefer to reword. In the early days of move this was the prevailing thought. I.e. you can’t do anything with a moved-from object but destruct it! But that really isn’t right. After all, if that were true, std::swap wouldn’t swap values, it would just destruct them. :-) That is, std::swap brings moved-from values “back from the dead”.

Indeed, during a sort, everything gets moved, and every moved-from value is given a new one.

> None of this is to say that the design of move is wrong.

No worries, I’m not interpreting your thoughts that way.

> But what bothers me is that even for my simple example, the result is that apparently (listening to the replies already in) can't guarantee even that code will reliably output the same thing on different platforms AND that no compiler I have seen currently diagnoses the issue. It may not even be possible. That's what worries me.

Your worries are not without justification.

> Your earlier question acknowledges the kinds of things that need to be answered which ever road you go down and the complexity of answering that which is why I haven't tried.
>
> All I'm doing is just laying out what I see as the current state and saying they seem less than ideal and asking what can we do about it. I want the compiler to be able to spot errant use after move as much as we spot errant use after delete and/or to make sensible use after move less platform specific. There may be no perfect here. But better might be obtainable. I like move I just want to make using it less of a source of bugs and am interested in ways of achieving that if it's possible.

I appreciate your efforts in this area. I am hearing from multiple sources that std::move is overused and that moved-from values are not being correctly handled. Right now I do not know what the solution is, nor if a solution is possible, nor even if this is something that needs to be solved beyond education on std::move. I remain interested in the domain, and will contribute whatever background knowledge I can towards everyone's efforts to better C++.

Howard

David Krauss

unread,
Apr 22, 2015, 11:59:34 PM4/22/15
to std-dis...@isocpp.org

On 2015–04–22, at 10:59 PM, Nevin Liber <ne...@eviloverlord.com> wrote:

And what about calls which only conditionally do a move, such as set<T>::insert(T&&)?

I don’t think the standard has a notion of conditional moving. You always have to assume that the object has been moved-from.

It would be nice to see standardization of conditional moves, but the optimization is patchily applied in practice. I made the mistake once of trying to move an object into a map, moving it somewhere else upon key collision. It went into the bit-bucket instead.

Requiring that moved-from objects be reset to default-initialized state would entail some pessimization. Until the implementations and the standard can converge on some best behavior, unspecified behavior looks like the right choice.

gmis...@gmail.com

unread,
Apr 23, 2015, 2:27:15 AM4/23/15
to std-dis...@isocpp.org


On Thursday, April 23, 2015 at 1:09:40 PM UTC+12, Howard Hinnant wrote:
On Apr 22, 2015, at 8:48 PM, gmis...@gmail.com wrote:
>
> And secondly, once applied, it obliterates the object pretty profoundly.

This is a notion I’d prefer to reword.  In the early days of move this was the prevailing thought.  I.e. you can’t do anything with a moved-from object but destruct it!  But that really isn’t right.  After all, if that were true, std::swap wouldn’t swap values, it would just destruct them. :-)  That is, std::swap brings moved-from values “back from the dead”.

This aspect is definitely the interesting thing. My own experience was the opposite way around: first I believed that "of course you can reuse an object after moving from it, within reason*", as that just seemed sensible.

But then my colleagues would say "oh, no you must never use an object *at all* after it's been moved from" and I would think, hmmm, ok, that's simple to remember, but seems a bit draconian and unnatural.

But to my mind it starts to get really worrisome if you have a situation where people can't even agree or be sure if a piece of code as simple as my example should work AND no compiler diagnose the problem which is where I think we are today.

And whatever view you take, if things aren't clear people and people start rewriting code to reflect their own opinion, that isn't good either.

The worst part for me is that I have found that my own view oscillates between the above two views for various reasons.

My view point 1 is that an object that has been moved from should generally attempt to put itself into a state that it has the same usability guarantees as if has if it had been default constructed. This model seems natural and it would fix a certain source of bugs and it would make the simple string example work consistently which I find appealing.

But my contrary view says that offering any such a guarantee might be sub-optimal because my guess is that most objects are never used again after they have been moved from so not having to reset to a definite state might improve performance.

And the most bothersome kind of code is code that conditionally moves (either by error or by design) because that seems to invite a dangerous source of bugs - i.e. where you can end up appending to a string you thought was empty but it turns out that it isn't because a move didn't happen for some reason.

Adopting a clear "don't re-use after move" rule would seem to help in the latter cases but it makes my simple code example unreliable and I hate that.

But whatever we recommend, I think it would be a significant win if the compiler was able to spot when I'm doing it wrong.

Off the top of my head, maybe the default should be that objects are considered not reusable and any function that can move should be considered to have moved by default and the compiler should be mandated to issue a warning if you attempt to reuse an object without being explicit.

For example, imagine if this were the default:

void take_me_lord(my_object&& ashes_to_ashes);
my_object goodbye_cruel_world("so long");
take_me_lord( goodbye_cruel_world );
goodbye_cruel_world = "resurrection"; // COMPILER ERROR:

But to get around it you had to write:

void take_me_lord(my_object&& ashes_to_ashes);
my_object goodbye_cruel_world("so long");
take_me_lord( goodbye_cruel_world );
reusing goodbye_cruel_world; // New construct.
goodbye_cruel_world = "resurrection"; // It works, it's a miracle.

The re-using statement could be any number of things:
1. just a device to prevent a compiler warning.
2. or it could do something like call a re-constructor.

A re-constructor could put an existing object into a state where it can be destructed or reused as if it had just been constructed.
The default implementation could be:
template<T> void reconstruct( T& existing_obj )
{
~existing_obj();
new(existing_obj)();
}
but the user could provide a more efficient implementation where it made sense.
e.g. a string could just sets it's length to 0 and then that code could be omitted from it's move functions.

Anyway, I'm brain storming off the top of my head. I haven't thought about any of that too deeply as to if it makes sense or is actually viable or is just plane worse than what we have already.

But it would seem to enable the compiler to immediately say that anything that appears after an std::move(x) is an error unless it is followed by a re-using statement to explicitly ignore that warning and maybe do something more and it would seem to allow some move operations to be simpler so that they don't incur a cost unless a re-use actually happens and it makes re-use intent clear and explicit.

Just some thoughts to help debate.


Indeed, during a sort, everything gets moved, and every moved-from value is given a new one.

> None of this is to say that the design of move is wrong.

No worries, I’m not interpreting your thoughts that way.

> But what bothers me is that even for my simple example, the result is that apparently (listening to the replies already in) can't guarantee even that code will reliably output the same thing on different platforms AND that no compiler I have seen currently diagnoses the issue. It may not even be possible. That's what worries me.

Your worries are not without justification.

> Your earlier question acknowledges the kinds of things that need to be answered which ever road you go down and the complexity of answering that which is why I haven't tried.
>
> All I'm doing is just laying out what I see as the current state and saying they seem less than ideal and asking what can we do about it. I want the compiler to be able to spot errant use after move as much as we spot errant use after delete and/or to make sensible use after move less platform specific. There may be no perfect here. But better might be obtainable. I like move I just want to make using it less of a source of bugs and am interested in ways of achieving that if it's possible.

I appreciate your efforts in this area.  I am hearing from multiple sources that std::move is overused and that moved-from values are not being correctly handled.  Right now I do not know what the solution is, nor if a solution is possible, nor even if this is something that needs to be solved beyond education on std::move.  I remain interested in the domain, and will contribute whatever background knowledge I can towards everyone's efforts to better C++.

Howard

Thanks Howard. Move is a great feature. It'll be cool if it could be made even better, but I appreciate it might not be trivial to uncover exactly how to do that.

Greg Marr

unread,
Apr 23, 2015, 4:33:35 PM4/23/15
to std-dis...@isocpp.org
On Thursday, April 23, 2015 at 2:27:15 AM UTC-4, gmis...@gmail.com wrote:
My view point 1 is that an object that has been moved from should generally attempt to put itself into a state that it has the same usability guarantees as if has if it had been default constructed. This model seems natural and it would fix a certain source of bugs and it would make the simple string example work consistently which I find appealing.

That sounds very similar to a valid but unspecified state.  The difference being just that you don't know what's in the object, and by using std::move() you've explicitly said that you don't care, because you're either going to destroy it or sets its state to something known.

But my contrary view says that offering any such a guarantee might be sub-optimal because my guess is that most objects are never used again after they have been moved from so not having to reset to a definite state might improve performance.

And the most bothersome kind of code is code that conditionally moves (either by error or by design) because that seems to invite a dangerous source of bugs - i.e. where you can end up appending to a string you thought was empty but it turns out that it isn't because a move didn't happen for some reason.

Adopting a clear "don't re-use after move" rule would seem to help in the latter cases but it makes my simple code example unreliable and I hate that.

It's not "don't re-use after move", it's "don't re-use after move until you've put it into a known state". 

For example, imagine if this were the default:

void take_me_lord(my_object&& ashes_to_ashes);
my_object goodbye_cruel_world("so long");
take_me_lord( goodbye_cruel_world );
goodbye_cruel_world = "resurrection"; // COMPILER ERROR:

That being a compiler error would be very bad.  That is perfectly valid code today for well-behaved classes such as std::string, assuming the addition of the missing std::move() in the take_me_lord() call, since it won't compile as-is because it's not an rvalue.  I've heard several people say, and I agree, that if you read std::move() as std::rvalue_cast(), then it makes more sense that you're not actually moving when you do std::move(), you're telling the called function that it's okay to move from this object.

A re-constructor could put an existing object into a state where it can be destructed or reused as if it had just been constructed.

You don't need a re-constructor for this, as this is mostly what already happens.  You can reuse it as long as you use a function that sets the entire state, and not one that simply modifies the existing state.  So you can use std::string::operator=(), but std::string::operator+=() will give you unspecified results.

gmis...@gmail.com

unread,
Apr 23, 2015, 6:14:38 PM4/23/15
to std-dis...@isocpp.org


On Friday, April 24, 2015 at 8:33:35 AM UTC+12, Greg Marr wrote:
On Thursday, April 23, 2015 at 2:27:15 AM UTC-4, gmis...@gmail.com wrote:
My view point 1 is that an object that has been moved from should generally attempt to put itself into a state that it has the same usability guarantees as if has if it had been default constructed. This model seems natural and it would fix a certain source of bugs and it would make the simple string example work consistently which I find appealing.

That sounds very similar to a valid but unspecified state.  The difference being just that you don't know what's in the object, and by using std::move() you've explicitly said that you don't care, because you're either going to destroy it or sets its state to something known.

The difference is consistency - programmers mental model. Currently, after a move (such as on a string) a types "valid but unspecified state is" so unspecified that even my simple string example from earlier can't be guaranteed to work i.e. even length cannot be guaranteed to be 0. But because it feels natural that a string should be empty after a move and because it usually does turn out to be in practice programmers are making the mistake of relying on that. If you changed the implementation of string to artificially change length to a random number after move, I think a lot of code would break / you'd find a lot of bugs.

If types could be generally said to reset to their default initialized state this would fix some of those bugs and make a clearer mental model as to what methods are valid after move, because if you are familiar with a type to use it, you are familiar with it's default initialized state to know what you can do with it.

The ideas was about attempting to make more methods valid after use a move and an off the top of my head idea aimed at making the type of code I already see "out there" like the string example that is currently technically wrong, valid again.

It's not an idea I am strongly attached to, I am actually trying to branch out to explore to opposite directions either or neither of which might improve on the status quo.
So this idea is about trying to find new ideas for discussion about the issue.
And the answer to your next question relates to the exploration of the other direction.
 

But my contrary view says that offering any such a guarantee might be sub-optimal because my guess is that most objects are never used again after they have been moved from so not having to reset to a definite state might improve performance.

And the most bothersome kind of code is code that conditionally moves (either by error or by design) because that seems to invite a dangerous source of bugs - i.e. where you can end up appending to a string you thought was empty but it turns out that it isn't because a move didn't happen for some reason.

Adopting a clear "don't re-use after move" rule would seem to help in the latter cases but it makes my simple code example unreliable and I hate that.

It's not "don't re-use after move", it's "don't re-use after move until you've put it into a known state". 

Yes I know. But the "problem" here is that the compiler has no idea here *which* function(s) will put the object into a state where it is more reusable again and consequently if you start using ones you shouldn't, it can't warn you.

I want to find a way if possible to allow the compiler to say "hey you probably have just moved from this type and now you are calling methods that probably aren't legal yet, call one of these methods first to make it legal".

So for example, if we could decorate functions with an attribute that says "call me first after move" the idea is the compiler assumes that after a move, an object is likely fragile, so if you call just any function, it's likely to not be correct and it'll warn you, but it can see you have called one of these other "resurrection" type attributed functions first, then it could supress that warning.
 

For example, imagine if this were the default:

void take_me_lord(my_object&& ashes_to_ashes);
my_object goodbye_cruel_world("so long");
take_me_lord( goodbye_cruel_world );
goodbye_cruel_world = "resurrection"; // COMPILER ERROR:

That being a compiler error would be very bad.  That is perfectly valid code today for well-behaved classes such as std::string, assuming the addition of the missing std::move() in the take_me_lord() call, since it won't compile as-is because it's not an rvalue.  I've heard several people say, and I agree, that if you read std::move() as std::rvalue_cast(), then it makes more sense that you're not actually moving when you do std::move(), you're telling the called function that it's okay to move from this object.

A re-constructor could put an existing object into a state where it can be destructed or reused as if it had just been constructed.

You don't need a re-constructor for this, as this is mostly what already happens.  You can reuse it as long as you use a function that sets the entire state, and not one that simply modifies the existing state.  So you can use std::string::operator=(), but std::string::operator+=() will give you unspecified results.


So you can see the goals are to try to make more currently unreliable code like my string example reliable if possible and to make the compiler better able to stop you making this kinds of mistakes in the first place.

My two ideas were off the top of my head thoughts to start discussion on that. But they were looking in opposite directions for the answer. One is to try to make types more consistent after move such that a broader range of operations would just be valid by default using a mental model the programmer can imagine easily for their type - they know it's default constructed state.

And the other idea was to let the compiler in on what functions need to be called to resurrect a class and not just assume any function was ok. In essence to try to provide the compiler with knowledge of the protocol we expect to follow for types where possible so it can inform us when we violate that.

If we don't improve on the situation as it is today we have bugs like my string example going undetected and the 'just don't use it again after a move' starts to become the safest thing do to avoid bugs even though that's technically that is overkill.

Neither idea once here may be appropriate I was just exploring off the top of my head. But I think I have articulated the goal I am aiming at quite clearly - let's try to see if we can do more to reduce 'incorrect use after move' errors and turn more of them into compile time errors.

Nevin Liber

unread,
Apr 23, 2015, 10:30:00 PM4/23/15
to std-dis...@isocpp.org
On 23 April 2015 at 17:14, <gmis...@gmail.com> wrote:


On Friday, April 24, 2015 at 8:33:35 AM UTC+12, Greg Marr wrote:
On Thursday, April 23, 2015 at 2:27:15 AM UTC-4, gmis...@gmail.com wrote:
My view point 1 is that an object that has been moved from should generally attempt to put itself into a state that it has the same usability guarantees as if has if it had been default constructed. This model seems natural and it would fix a certain source of bugs and it would make the simple string example work consistently which I find appealing.

That sounds very similar to a valid but unspecified state.  The difference being just that you don't know what's in the object, and by using std::move() you've explicitly said that you don't care, because you're either going to destroy it or sets its state to something known.

The difference is consistency - programmers mental model.

Not fitting your mental model is not a bug in the standard.

I don't know how you solve the problem of people not reading documentation, because the number of different mental models that people can make up that don't fit the specification is unbounded.
 
Currently, after a move (such as on a string) a types "valid but unspecified state is" so unspecified that even my simple string example from earlier can't be guaranteed to work i.e. even length cannot be guaranteed to be 0.

Because unspecified means not specified.  I really don't know how it can be made any clearer.  Would you prefer we pick a different word out of a thesaurus?  You seem to think that unspecified should mean specified.

But because it feels natural that a string should be empty after a move and because it usually does turn out to be in practice programmers are making the mistake of relying on that. If you changed the implementation of string to artificially change length to a random number after move, I think a lot of code would break / you'd find a lot of bugs.

Many people have an oversimplified mental model of the underlying machine too, with no caches and atomic access to each and every primitive type.  They sprinkle "volatile" all over their variables because they think it magically solves threading and concurrency problems.  What should we do about that, as the bugs it causes are far more insidious?

If types could be generally said to reset to their default initialized state this would fix some of those bugs

But not all of them, because the mental model would still be wrong. It would just make correct code slower.

Your mental model is inconsistent with what an arbitrary developer can do with his/her move constructor for a user defined type.

Plus, there are reasons to use r-value references besides intent to move.  It indicates an unnamed temporary, and can be useful in things like expression templates, where the classes should not be holding on to references/pointers past the full expression it is a part of.
 
If we don't improve on the situation as it is today we have bugs like my string example going undetected and the 'just don't use it again after a move' starts to become the safest thing do to avoid bugs even though that's technically that is overkill.

What's wrong with that, other than it being overly pessimistic?

Over time, you can teach them a better mental model: 

  • Assignment will put the object back into a specific state and makes it once again effectively useable.
  • For standard library containers, calling clear() will also the object back into a specific state and makes it once again effectively useable.

While that may not always be 100% optimal, IMNSHO anything more fine-grained needs a measured performance bottleneck before pursuing.


Or you can write your own library that matches your mental model.

Ross Smith

unread,
Apr 23, 2015, 11:02:04 PM4/23/15
to std-dis...@isocpp.org
On 2015-04-24 10:14, gmis...@gmail.com wrote:
>
> The difference is consistency - programmers mental model. Currently,
> after a move (such as on a string) a types "valid but unspecified state
> is" so unspecified that even my simple string example from earlier can't
> be guaranteed to work i.e. even length cannot be guaranteed to be 0.
> But because it feels natural that a string should be empty after a
> move and because it usually does turn out to be in practice programmers
> are making the mistake of relying on that. If you changed the
> implementation of string to artificially change length to a random
> number after move, I think a lot of code would break / you'd find a lot
> of bugs.
>
> If types could be generally said to reset to their default initialized
> state this would fix some of those bugs and make a clearer mental model
> as to what methods are valid after move, because if you are familiar
> with a type to use it, you are familiar with it's default initialized
> state to know what you can do with it.
>
> The ideas was about attempting to make more methods valid after use a
> move and an off the top of my head idea aimed at making the type of code
> I already see "out there" like the string example that is currently
> technically wrong, valid again.

There's a difference between invalid code and code that is valid but you
don't know what it will do. The standard guarantees that a moved-from
string "is left in a valid state with an unspecified value". That just
means it's an ordinary string whose contents you don't know; it doesn't
mean there's some sort of restriction on what you can do with it (aside
from operations that aren't guaranteed valid on every string anyway,
such as front()). In many ways a moved-from string is like a string
passed in to a function: you don't know what's in it, but you can trust
it to be, well, a string.

Consider this example:

void repchar(string& s, size_t n, char c) {
// Set the string to n copies of c
auto oldsize = s.size();
s.resize(n, c);
fill_n(s.begin(), min(n, oldsize), c);
}

(Yes, I know there's a version of string::assign() that already does
this, but I implemented it that way to make a point.)

Now suppose I write this code:

string s1, s2;
s1 = "Hello";
s2 = move(s1);
repchar(s1, 5, '!');
cout << s2 << s1 << '\n';

Can I trust that to print "Hello!!!!!"? Yes, I think I can, given the
current standard's spec for strings. The repchar() function never calls
anything like clear() that might be considered a "reset to a known
state" function, but repchar() can be expected to work perfectly well on
a string with arbitrary but known contents, so I think it can reasonably
be expected to work the same way on a moved-from string. A moved-from
string is just a string with unknown but valid content, not some kind of
delicate fragile half-constructed value that's only usable in certain
contexts.

I don't think there's anything wrong with the current specification of
move semantics, or that there's anything surprising about this
behaviour. Naively, I would expect a moved-from string to still contain
its original contents if it was short, but to be empty if it was long
(because of the short string optimization); I haven't tested this on any
real compiler. I don't see any problem with that behaviour, and would
not be happy if my compiler took it into its head to warn me about it,
or if some future iteration of the standard required less efficient
behaviour because some people thought it was "surprising".

(I believe the short vector optimization is not legal on std::vector,
because of the requirement that iterators remain valid after swap(), but
I don't think that applies to std::string.)

Ross Smith


Vicente J. Botet Escriba

unread,
Apr 24, 2015, 2:01:39 AM4/24/15
to std-dis...@isocpp.org
Le 17/04/15 03:02, Nathan Ernst a écrit :
(This question is based upon document ISO/IEC 14882:2011(E))

I'm writing a parser that's going to end up generating lots of instances of standard strings and vectors, and I'm trying to be as unobtrusive in terms of memory as I can.  I'm trying to move the generated strings and vectors for obvious reasons, but it got me thinking about the state of the the carcass of the moved instance.

For strings, I found the language in § 21.4.2 paragraph 2 that "In the second form, str is left in a valid state with an unspecified value." The second form refers to the the rvalue-reference constructor of basic_string.

From this language, I expect I should be able to call methods on a previously moved instance without introducing undefined behavior, but the result of those calls are unspecified (calling c_str could return an empty string or anything else). What I gather from this is that if I wanted to reuse a previously moved string instance, I would need re-initialize it in order to get to a known state.

As an example, if I wrote:

std::function<void(std::string&&)> sink = ...;
std::string s;

for (int i = 0; i < 26; ++i)
{
 s.append('a' + i);
 sink(std::move(s));
}

It would be conformant for sink to receive "a", "b", "c".... or to receive "a", "ab", "abc", or really anything else after initially receiving "a".

Question boils down to: To be in a known & valid state, do I need to reinitialize a moved type in order to be able to guarantee behavior?

i.e. Do I need to do the following in order to ensure sink receives "a", "b", "c"...:

std::function<void(std::string&&)> sink = ...;
std::string s;

for (int i = 0; i < 26; ++i)
{
 s.append('a' + i);
 sink(std::move(s));
 s = std::string();  // possibly redundant?
}

In general would this assumption hold across all standard movable types? Is the extra re-initialization necessary to ensure cross platform/implementation behavior?


Hi,

you could do
std::function<void(std::string&&)> sink = ...;


for (int i = 0; i < 26; ++i)
{
 std::string s; = ('a' + i);
 sink(std::move(s));
}


Anyway, the problem of portability should be solved with Contracts. Contracts will show you in one way or another that your code is invalid.

Vicente

gmis...@gmail.com

unread,
Apr 24, 2015, 7:59:36 AM4/24/15
to std-dis...@isocpp.org


On Friday, April 24, 2015 at 2:30:00 PM UTC+12, Nevin ":-)" Liber wrote:
On 23 April 2015 at 17:14, <gmis...@gmail.com> wrote:


On Friday, April 24, 2015 at 8:33:35 AM UTC+12, Greg Marr wrote:
On Thursday, April 23, 2015 at 2:27:15 AM UTC-4, gmis...@gmail.com wrote:
My view point 1 is that an object that has been moved from should generally attempt to put itself into a state that it has the same usability guarantees as if has if it had been default constructed. This model seems natural and it would fix a certain source of bugs and it would make the simple string example work consistently which I find appealing.

That sounds very similar to a valid but unspecified state.  The difference being just that you don't know what's in the object, and by using std::move() you've explicitly said that you don't care, because you're either going to destroy it or sets its state to something known.

The difference is consistency - programmers mental model.

Not fitting your mental model is not a bug in the standard.

Well that is arguable depending on how you want to determine what a bug is
A moved from string that has a length that is unspecified or that has to be assigned and empty string to be usable is odd at first inspection.
One could argue that it's an unintuitive design that means it may as well be a bug in the standard in the same way that auto_ptr was.
If it leads to a lot of bugs maybe it needs to be replaced with something that doesn't do that  so easily in the same way unique_ptr was added.


I don't know how you solve the problem of people not reading documentation, because the number of different mental models that people can make up that don't fit the specification is unbounded.

Nor do I. But we could say the same about use after free, but it didn't stop people from building better schemes to detect that. That's all I'm suggesting look into. Let's not be hostile to that.
 
Currently, after a move (such as on a string) a types "valid but unspecified state is" so unspecified that even my simple string example from earlier can't be guaranteed to work i.e. even length cannot be guaranteed to be 0.

Because unspecified means not specified.  I really don't know how it can be made any clearer.  Would you prefer we pick a different word out of a thesaurus?  You seem to think that unspecified should mean specified.
 
I know what it means. I've been clear about what I think the issue is even a solution for it might not be easy or possible. But you getting hung up on words doesn't help.

But because it feels natural that a string should be empty after a move and because it usually does turn out to be in practice programmers are making the mistake of relying on that. If you changed the implementation of string to artificially change length to a random number after move, I think a lot of code would break / you'd find a lot of bugs.

Many people have an oversimplified mental model of the underlying machine too, with no caches and atomic access to each and every primitive type.  They sprinkle "volatile" all over their variables because they think it magically solves threading and concurrency problems.  What should we do about that, as the bugs it causes are far more insidious?

If types could be generally said to reset to their default initialized state this would fix some of those bugs

But not all of them, because the mental model would still be wrong. It would just make correct code slower.

Your mental model is inconsistent with what an arbitrary developer can do with his/her move constructor for a user defined type.

Plus, there are reasons to use r-value references besides intent to move.  It indicates an unnamed temporary, and can be useful in things like expression templates, where the classes should not be holding on to references/pointers past the full expression it is a part of.
 
If we don't improve on the situation as it is today we have bugs like my string example going undetected and the 'just don't use it again after a move' starts to become the safest thing do to avoid bugs even though that's technically that is overkill.

What's wrong with that, other than it being overly pessimistic?

Over time, you can teach them a better mental model: 

  • Assignment will put the object back into a specific state and makes it once again effectively useable.
  • For standard library containers, calling clear() will also the object back into a specific state and makes it once again effectively useable.

While that may not always be 100% optimal, IMNSHO anything more fine-grained needs a measured performance bottleneck before pursuing.

The issue isn't about performance, it's about reducing bugs related to using move.
The only issue related to performance is making any solution doesn't degrade performance as to make it unviable.

Or you can write your own library that matches your mental model.

Again this misses the point. Something that just helps my own mental model but doesn't gel with everyone else's is too niche to bother with.

gmis...@gmail.com

unread,
Apr 24, 2015, 8:09:19 AM4/24/15
to std-dis...@isocpp.org, ro...@pharos.co.nz
I don't think you can. But if I'm wrong about that it wouldn't change my view point. But it might change yours.
There is no guarantee what size() would be. That's certainly what others have concurred with at the start of this discussion.

 
The repchar() function never calls
anything like clear() that might be considered a "reset to a known
state" function, but repchar() can be expected to work perfectly well on
a string with arbitrary but known contents, so I think it can reasonably
be expected to work the same way on a moved-from string. A moved-from
string is just a string with unknown but valid content, not some kind of
delicate fragile half-constructed value that's only usable in certain
contexts.

I don't think there's anything wrong with the current specification of
move semantics,

I've been clear to say all along that I don't think there is anything wrong with move semantics either.
You seem to be missing what I've said.
I think you aren't focused on the issue you are focused on whether my off the top of my head ideas as possible solution directions to the issue are correct at the expense of the issue.
I recommend focus on the issue I am talking about and maybe you'll have a better idea there. If there is no solution then fine.
But trying to improve X doesn't mean that X is wrong. It just means asking the question can X be better.
There's nothing wrong with delete either, but it didn't stop being trying to find ways of improving things so that use after delete could be found. 

David Rodríguez Ibeas

unread,
Apr 24, 2015, 10:04:59 AM4/24/15
to std-dis...@isocpp.org
On Fri, Apr 24, 2015 at 12:59 PM, <gmis...@gmail.com> wrote:
A moved from string that has a length that is unspecified or that has to be assigned and empty string to be usable is odd at first inspection.

An 'int' that is uninitialized and has to be assigned to in order to be usable is odd at first impression.


The issue isn't about performance, it's about reducing bugs related to using move.

But it is.  There is a balance between the guarantees that you want to provide and the cost that providing those guarantees impose on the program.  The main use of functions taking rvalue-references is to take advantage of an rvalue that won't be used after this operation, yes, you can bind an rvalue-reference to an lvalue and steal from the lvalue, but that is not the primary use case.  Adding code that resets the string to a well known state would make every use of the move operations incur a cost, small as it may be, that is not needed.

Consider a large vector<string>, say 10M elements, and erasing the first element. Do you want to trigger 10M 'clear()' followed by move assignments? I'd rather avoid the 'clear' as the move assignments will guarantee the final value without going through some intermediate guaranteed to be empty state.

    David

Thiago Macieira

unread,
Apr 24, 2015, 12:04:39 PM4/24/15
to std-dis...@isocpp.org
On Friday 24 April 2015 04:59:36 gmis...@gmail.com wrote:
> The issue isn't about performance, it's about reducing bugs related to
> using move.

Then it's a QoI issue: please file a suggestion for improvement to your
Standard Library implementation and request that, in debug mode, they detect
this scenario and abort execution with a failed assertion.

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358

gmis...@gmail.com

unread,
Apr 24, 2015, 7:17:59 PM4/24/15
to std-dis...@isocpp.org, dib...@ieee.org


On Saturday, April 25, 2015 at 2:04:59 AM UTC+12, David Rodríguez Ibeas wrote:

On Fri, Apr 24, 2015 at 12:59 PM, <gmis...@gmail.com> wrote:
A moved from string that has a length that is unspecified or that has to be assigned and empty string to be usable is odd at first inspection.

An 'int' that is uninitialized and has to be assigned to in order to be usable is odd at first impression.


The issue isn't about performance, it's about reducing bugs related to using move.

But it is.  There is a balance between the guarantees that you want to provide and the cost that providing those guarantees impose on the program.

If you take the full sentence of what you have cut short I acknowledge that there is a possible tension. But never the less performance isn't the focus of the issue - i.e. I am not trying to make programs faster. It is a goal not to make them slower though such that it makes the true goal of reducing move bugs unattainable. Your comments only echo what I am saying but in a way that makes it sound like I'm saying something else.

 
  The main use of functions taking rvalue-references is to take advantage of an rvalue that won't be used after this operation, yes, you can bind an rvalue-reference to an lvalue and steal from the lvalue, but that is not the primary use case.  Adding code that resets the string to a well known state would make every use of the move operations incur a cost, small as it may be, that is not needed.

Consider a large vector<string>, say 10M elements, and erasing the first element. Do you want to trigger 10M 'clear()' followed by move assignments? I'd rather avoid the 'clear' as the move assignments will guarantee the final value without going through some intermediate guaranteed to be empty state.

No I don't want to trigger that but there maybe a trade off of cost that is acceptable. I doubt it, but that's part of the debate and I was drawing attention to that.


    David

gmis...@gmail.com

unread,
Apr 24, 2015, 7:33:42 PM4/24/15
to std-dis...@isocpp.org
Quality is factor here - reducing likelihood of bugs - but first I don't have any proven solution to suggest, That's why I'm discussing the issue here to help evolve one with brighter minds than me.
But second, it's just plain slack to just say oh write to your vendor. How does that help anybody else?

Let's say I did that, they'd just take that, augment it to their liking and apply it to their library in some non standard way and who else benefits from that? Not even me.

It would just encourage more of the proprietary designators we already have today like the in/out and length type attributes that vendors already litter their libraries with. But nobody else can use. These are all features that might be useful to ones own libraries and types and for cross platform code yet we can't use any of them because each vendor has their own brand of it - probably because someone wrote to their own vendor each time and we got a multitude of solutions that nobody can use other than the vendor. So no, writing to my vendor is specifically NOT want I want or plan to do.
 

Thiago Macieira

unread,
Apr 25, 2015, 12:41:06 PM4/25/15
to std-dis...@isocpp.org
On Friday 24 April 2015 16:33:42 gmis...@gmail.com wrote:
> Quality is factor here - reducing likelihood of bugs - but first I don't
> have any proven solution to suggest, That's why I'm discussing the issue
> here to help evolve one with brighter minds than me.
> But second, it's just plain slack to just say oh write to your vendor. How
> does that help anybody else?
>
> Let's say I did that, they'd just take that, augment it to their liking and
> apply it to their library in some non standard way and who else benefits
> from that? Not even me.
>
> It would just encourage more of the proprietary designators we already have
> today like the in/out and length type attributes that vendors already
> litter their libraries with. But nobody else can use. These are
> all features that might be useful to ones own libraries and types
> and for cross platform code yet we can't use any of them because each
> vendor has their own brand of it - probably because someone wrote to their
> own vendor each time and we got a multitude of solutions that nobody can
> use other than the vendor. So no, writing to my vendor is specifically NOT
> want I want or plan to do.

My point was that the discussion seemed to wind down to the point where there
would be no changes to the standard: doing anything of what you propose would
be a performance hit for everyone who already has right code and has followed
the guidance since C++11 and move semantics came out.

So the next step is to see if you can ask your vendor to abort() execution in
debug mode. The solution would not be an extension: all you would need to do
to enable it would be to *not* define NDEBUG. Debug mode code often overwrites
buffers in free() with a known value and pre-initialises variables left
uninitialised with a known pattern too. So why not this?
Reply all
Reply to author
Forward
0 new messages