Date: Mon, 16 Dec 2024 19:40:36 +0100
The move operation must make sure that the object remains valid for
reassignment and destruction, as it is for uninitialized variables. But
that should be the only requirements.
For example std::vector's move operations must set the pointer to the
data to nullptr, so the destructor doesn't free the memory that is in
use by the other object that was moved into.
Currently the move operations also is required by the standard to
restore all invariants, to make the object have an "unspecified but
valid state", such as setting the size to 0 in vector, or re-allocating
memory for sentinel nodes in std::list. This is needless work done on an
object that you said explicitly in your program that you are going to
discard anyway. Therefore not discarding it and keep using it is
logically meaningless.
My proposal is to remove this additional requirement to restore the
invariants only, not the requirement that the object need to be
destructible (or reassignable). This is to return to performance
oriented c++.
A safe abstraction can always be put around a fast one, but you can't
make something that is needlessly safe and slower faster. That is the
domain of other languages to provide a safe and potentially slow default.
On 16.12.24 19:24, Andre Kostur via Std-Proposals wrote:
> If the object is in an invalid state, how do you know that it is even
> destructible?
>
> On Mon, Dec 16, 2024 at 10:15 AM Paul Meckel via Std-Proposals
> <std-proposals_at_lists.isocpp.org> wrote:
>
> Hi.
>
> I propose that the requirements of "moved-from" objects belonging
> to the
> standard library are relaxed from "unspecified but valid" to
> "unspecified, and potentially invalid" with "the use of their state
> results in undefined behavior".
>
> My reasoning for this proposal is this:
>
> Moving from an object occurs in two scenarios only:
>
> 1. Moving from a temporary, in which case this doesn't apply
> 2. Moving explicitly, for example with std::move
>
> In both cases, the programmer indicates that the moved-from object
> is no
> longer important to them, and that it's resources may be used. As
> such
> the only two valid operations on these objects should be the same two
> valid operations as on uninitialized variables, as they are
> conceptually
> the same: assignment and destruction. Anything else makes no
> sense, and
> there is no use case.
>
> The problem is that the c++ standard places requirements on these
> moved
> from objects if they are types of the standard library. For example
> std::vector must be in an "unspecified but valid state", where valid
> means that invariants must hold for the moved from object. This in
> turn
> means that all operations on that object have to be safe. .size() or
> .push_back() need to perform as expected, even though using these
> operations on a moved from object is nonsense.
>
> This places a burden on the library implementers to ensure invariants
> are not violated, and more importantly it violates the core
> principle of
> c++ as a language: uncompromising performance, even at the cost of
> safety. This is why uninitialized primitive type variables exist,
> even
> though initializing them to a default value would prevent a lot of
> bugs.
> c++ is about performance first, and any safety on top of that is
> optional and at no cost if not used. This is why we have both
> operator[]
> and member function .at() for std::array.
>
> Inherently, querying the state of, or using the value of a moved from
> object is semantically meaningless, as you just declared that you
> don't
> care about it anymore. A case could be made to allow some member
> functions, such as .clear() for vector, to remain valid even for
> moved
> from objects as it effectively resets the state; but a more
> semantically
> correct way would be reassignment of a new vector entirely.
>
> Therefore moving from an object and leaving it in an invalid
> state, if
> that saves performance, seems like a reasonable decision.
> Assigning 0 to
> the size member of a vector adds only one extra instruction per move
> (but so does default initializing primitive types, and that was
> deemed
> too much also), but for std::list, this means a dynamic memory
> allocation for a new sentinel node, which in turn may throw, which is
> why the move operations for std::list are not noexcept, even
> though move
> operations should be noexcept. So you did all that work, even
> reallocating memory, on an object that you know that the programmer
> intends to discard or reassign over. It makes no sense to perform
> that work.
>
> Impact on existing code:
>
> Code using moved-from object's state, such as relying on a vector
> being
> empty after a move is already a logic error, and a bug, as the
> state of
> the vector may not be empty. It is unspecified after all, even if in
> practice it very often is empty.
>
> Standard libraries do not need to implement these changes
> immediately,
> as the current safe behavior is also valid for undefined behavior.
>
> Correctly written code without the implicit logic error that comes
> with
> using a moved-from object's state remain valid. Exceptions are those
> which use a reset like function, such as .clear() for container
> types,
> instead of a reassignment.
>
>
> What do you think?
>
> Best Regards
>
> Paul
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
>
reassignment and destruction, as it is for uninitialized variables. But
that should be the only requirements.
For example std::vector's move operations must set the pointer to the
data to nullptr, so the destructor doesn't free the memory that is in
use by the other object that was moved into.
Currently the move operations also is required by the standard to
restore all invariants, to make the object have an "unspecified but
valid state", such as setting the size to 0 in vector, or re-allocating
memory for sentinel nodes in std::list. This is needless work done on an
object that you said explicitly in your program that you are going to
discard anyway. Therefore not discarding it and keep using it is
logically meaningless.
My proposal is to remove this additional requirement to restore the
invariants only, not the requirement that the object need to be
destructible (or reassignable). This is to return to performance
oriented c++.
A safe abstraction can always be put around a fast one, but you can't
make something that is needlessly safe and slower faster. That is the
domain of other languages to provide a safe and potentially slow default.
On 16.12.24 19:24, Andre Kostur via Std-Proposals wrote:
> If the object is in an invalid state, how do you know that it is even
> destructible?
>
> On Mon, Dec 16, 2024 at 10:15 AM Paul Meckel via Std-Proposals
> <std-proposals_at_lists.isocpp.org> wrote:
>
> Hi.
>
> I propose that the requirements of "moved-from" objects belonging
> to the
> standard library are relaxed from "unspecified but valid" to
> "unspecified, and potentially invalid" with "the use of their state
> results in undefined behavior".
>
> My reasoning for this proposal is this:
>
> Moving from an object occurs in two scenarios only:
>
> 1. Moving from a temporary, in which case this doesn't apply
> 2. Moving explicitly, for example with std::move
>
> In both cases, the programmer indicates that the moved-from object
> is no
> longer important to them, and that it's resources may be used. As
> such
> the only two valid operations on these objects should be the same two
> valid operations as on uninitialized variables, as they are
> conceptually
> the same: assignment and destruction. Anything else makes no
> sense, and
> there is no use case.
>
> The problem is that the c++ standard places requirements on these
> moved
> from objects if they are types of the standard library. For example
> std::vector must be in an "unspecified but valid state", where valid
> means that invariants must hold for the moved from object. This in
> turn
> means that all operations on that object have to be safe. .size() or
> .push_back() need to perform as expected, even though using these
> operations on a moved from object is nonsense.
>
> This places a burden on the library implementers to ensure invariants
> are not violated, and more importantly it violates the core
> principle of
> c++ as a language: uncompromising performance, even at the cost of
> safety. This is why uninitialized primitive type variables exist,
> even
> though initializing them to a default value would prevent a lot of
> bugs.
> c++ is about performance first, and any safety on top of that is
> optional and at no cost if not used. This is why we have both
> operator[]
> and member function .at() for std::array.
>
> Inherently, querying the state of, or using the value of a moved from
> object is semantically meaningless, as you just declared that you
> don't
> care about it anymore. A case could be made to allow some member
> functions, such as .clear() for vector, to remain valid even for
> moved
> from objects as it effectively resets the state; but a more
> semantically
> correct way would be reassignment of a new vector entirely.
>
> Therefore moving from an object and leaving it in an invalid
> state, if
> that saves performance, seems like a reasonable decision.
> Assigning 0 to
> the size member of a vector adds only one extra instruction per move
> (but so does default initializing primitive types, and that was
> deemed
> too much also), but for std::list, this means a dynamic memory
> allocation for a new sentinel node, which in turn may throw, which is
> why the move operations for std::list are not noexcept, even
> though move
> operations should be noexcept. So you did all that work, even
> reallocating memory, on an object that you know that the programmer
> intends to discard or reassign over. It makes no sense to perform
> that work.
>
> Impact on existing code:
>
> Code using moved-from object's state, such as relying on a vector
> being
> empty after a move is already a logic error, and a bug, as the
> state of
> the vector may not be empty. It is unspecified after all, even if in
> practice it very often is empty.
>
> Standard libraries do not need to implement these changes
> immediately,
> as the current safe behavior is also valid for undefined behavior.
>
> Correctly written code without the implicit logic error that comes
> with
> using a moved-from object's state remain valid. Exceptions are those
> which use a reset like function, such as .clear() for container
> types,
> instead of a reassignment.
>
>
> What do you think?
>
> Best Regards
>
> Paul
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
>
Received on 2024-12-16 18:40:43