Skip to content

[SUGGESTION] Mark moved-from objects as unitialised #246

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
AbhinavK00 opened this issue Jan 30, 2023 · 10 comments
Closed

[SUGGESTION] Mark moved-from objects as unitialised #246

AbhinavK00 opened this issue Jan 30, 2023 · 10 comments

Comments

@AbhinavK00
Copy link

This is a simple suggestion, track the use of moved-from objects just like cppfront does with unitialised objects so that there is a compile time error when using a moved-from object without an assignment after the move.
This is what we teach today, don't use moved-from objects without reassigning to them.
This will prevent errors due to access of moved-from objects and should be good enough reason to implement.
As a side note, what are the performance effects (both compile and run time) of tracking unitialised objects? And can we reuse the objects passed to function via move/forward?

@AbhinavK00 AbhinavK00 changed the title [SUGGESTION] Mark moved from objects as unitialised [SUGGESTION] Mark moved-from objects as unitialised Jan 30, 2023
@bluetarpmedia
Copy link
Contributor

bluetarpmedia commented Jan 31, 2023

It's not always necessary to assign to a moved-from object before using it again. For example, standard library objects are left in a "valid but unspecified state" (unless specified otherwise). This means you can call any of its member functions as long as they don't have preconditions. I.e. the state is valid, but we can't be sure exactly what it is.

User defined types might follow that same pattern, or have a stronger guarantee.

Here's an example from cppreference:

std::vector<std::string> v;
std::string str = "example";
v.push_back(std::move(str)); // str is now valid but unspecified
str.back(); // undefined behavior if size() == 0: back() has a precondition !empty()
if (!str.empty())
    str.back(); // OK, empty() has no precondition and back() precondition is met
 
str.clear(); // OK, clear() has no preconditions

The str.clear() line places the moved-from std::string back into a known state. It was valid before, but now we know exactly what state it's in.

So rather than preventing use of moved-from objects without reassigning first, I think the suggestion would have to be: prevent calling a member function with preconditions on a moved-from object, until the moved-from object is placed back into a well-known state. I'm not sure if such a feature is possible, except for hard-coding for specific well-known types.

@switch-blade-stuff
Copy link

I agree with @bluetarpmedia, since moved-from objects are not actually uninitialized, marking their lifetime as sich would break expectations, and if we would want to allow only specific set of functions to be used with moved-from objects, that would need native language support.

One way, however could be to have a Cpp2-only destructive move, that would essentially immediately destroy an object upon move, so that such objects could be marled as last use.

@AbhinavK00
Copy link
Author

I did originally consider to suggest about destructive move. But ultimately decided on this as destructive move prevents us from reusing the object but I'll be happy with destructive move if it is implemented. As for the other suggestion, I don't think it is feasible but again, I'll be happy with it too. But I would say that sometimes we have to reject some well-formed code to detect errors/undefined behaviour. That's the case with static analysers too in some cases.

@jcanizales
Copy link

What's the use case for allowing people to call such methods on moved-from objects? Is there an advantage of str.clear(); over str = ""; in that example?

@switch-blade-stuff
Copy link

In general case, assigning a string is most likely to be less efficient than clearing it (Jason Turner made a great video about it some time ago). In case of a moved-from string, which might be already cleared by the move itself, the compiler might see it and optimize the clear into a no-op.

It also signifies intent more clearly, and to me at least, assigning "" to strings looks quite ugly.

@AbhinavK00
Copy link
Author

I don't know much about compiler optimisation but can't the compiler also optimise str = " "; using the same logic? And the last point is just a matter of choice.

@AbhinavK00
Copy link
Author

AbhinavK00 commented Feb 19, 2023

Quoted from #221

A move parameter says that the callee will consume the value of the argument by moving it out, and the caller can pass any rvalue (but not an an lvalue, a named object where this is not its definite last use). That's important because if the caller is trying to pass a named variable (which means they could try to use the name again on the very next line), the caller needs to know they shouldn't try to use the object's value again before first resetting it to a new value... and because a move parameter accepts only an rvalue, the caller who tries to pass an lvalue will fail and has to explicitly write move to say 'trust me, treat this as an rvalue' to make it clear they know what they're getting into.

Excerpt from above

"That's important because if the caller is trying to pass a named variable (which means they could try to use the name again on the very next line), the caller needs to know they shouldn't try to use the object's value again before first resetting it to a new value"

That emphasizes my point of this suggestion, if the caller "shouldn't try to use the object's value again before first resetting it to a new value", then it just makes sense to not let them do that. And this can be extended to move-from assignments too.

@bluetarpmedia
Copy link
Contributor

If the caller "shouldn't try to use the object's value again before first resetting it to a new value", then it just makes sense to not let them do that. And this can be extended to move-from assignments too.

There are many ways to "reset to a new value" (i.e. place the moved-from object back into a well-known state). If I understand correctly, your proposal assumes that only an assignment after the move can reset it to a new value. But in the example I posted above (from cppreference) the str.clear() is sufficient to do that.

@AbhinavK00
Copy link
Author

AbhinavK00 commented Feb 20, 2023

I agree with you, this proposal is definitely about rejecting some perfectly-formed code to detect errors in related space.
But marking an object as unitialised doesn't mean that only assignments can be done to it, you can still pass the object as out parameter. And functions such as str.clear() do fall under that category (they don't read from the object, only assign to it), so they will still be allowed.
The current problem is that cpp does not implement parameter passing conventions and so cppfront cannot distinguish between functions (in standard library) which take an object as inout or out parameter and we end up having to allow only assignments. Maybe one day if the transpiler is able to make distinctions, then this feature will not be as restrictive.
And even with the current problem, I think it is worth it to make that tradeoff.

@hsutter
Copy link
Owner

hsutter commented Mar 1, 2023

Closing as by-design and explained in the comments... moved-from is not the same as uninitialized, and I am interested in catching use-of-moved-from but the Lifetime analysis is where I am to do that rather than the guaranteed initialization rules.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants