Does the standard define precisely what I can do with an object once it has been moved from? I used to think that all you can do with a moved-from object is do destruct it, but that would not be sufficient.
For example, take the function template swap as defined in the standard library:
template <typename T>
void swap(T& a, T& b)
{
T c = std::move(a); // line 1
a = std::move(b); // line 2: assignment to moved-from object!
b = std::move(c); // line 3: assignment to moved-from object!
}
Obviously, it must be possible to assign to moved-from objects, otherwise lines 2 and 3 would fail. So what else can I do with moved-from objects? Where exactly can I find these details in the standard?
(By the way, why is it T c = std::move(a); instead of T c(std::move(a)); in line 1?)
17.6.5.15 [lib.types.movedfrom]
Objects of types defined in the C++ standard library may be moved from
(12.8). Move operations may be explicitly specified or implicitly
generated. Unless otherwise specified, such moved-from objects shall
be placed in a valid but unspecified state.
When an object is in an unspecified state, you can perform any operation on the object which has no preconditions. If there is an operation with preconditions you wish to perform, you can not directly perform that operation because you do not know if the unspecified-state of the object satisfies the preconditions.
Examples of operations that generally do not have preconditions:
destruction
assignment
const observers such as get, empty, size
Examples of operations that generally do have preconditions:
dereference
pop_back
This answer now appears in video format here: http://www.youtube.com/watch?v=vLinb2fgkHk&t=47m10s
Moved-from objects exist in an unspecified, but valid, state. That suggests that whilst the object might not be capable of doing much anymore, all of its member functions should still exhibit defined behaviour — including operator= — and all its members in a defined state- and it still requires destruction. The Standard gives no specific definitions because it would be unique to each UDT, but you might be able to find specifications for Standard types. Some like containers are relatively obvious — they just move their contents around and an empty container is a well-defined valid state. Primitives don't modify the moved-from object.
Side note: I believe it's T c = std::move(a) so that if the move constructor (or copy constructor if no move is provided) is explicit the function will fail.
Related
I recently followed a Reddit discussion which lead to a nice comparison of std::visit optimization across compilers. I noticed the following: https://godbolt.org/z/D2Q5ED
Both GCC9 and Clang9 (I guess they share the same stdlib) do not generate code for checking and throwing a valueless exception when all types meet some conditions. This leads to way better codegen, hence I raised an issue with the MSVC STL and was presented with this code:
template <class T>
struct valueless_hack {
struct tag {};
operator T() const { throw tag{}; }
};
template<class First, class... Rest>
void make_valueless(std::variant<First, Rest...>& v) {
try { v.emplace<0>(valueless_hack<First>()); }
catch(typename valueless_hack<First>::tag const&) {}
}
The claim was, that this makes any variant valueless, and reading the docu it should:
First, destroys the currently contained value (if any). Then
direct-initializes the contained value as if constructing a value of
type T_I with the arguments std::forward<Args>(args).... If an
exception is thrown, *this may become valueless_by_exception.
What I don't understand: Why is it stated as "may"? Is it legal to stay in the old state if the whole operation throws? Because this is what GCC does:
// For suitably-small, trivially copyable types we can create temporaries
// on the stack and then memcpy them into place.
template<typename _Tp>
struct _Never_valueless_alt
: __and_<bool_constant<sizeof(_Tp) <= 256>, is_trivially_copyable<_Tp>>
{ };
And later it (conditionally) does something like:
T tmp = forward(args...);
reset();
construct(tmp);
// Or
variant tmp(inplace_index<I>, forward(args...));
*this = move(tmp);
Hence basically it creates a temporary, and if that succeeds copies/moves it into the real place.
IMO this is a violation of "First, destroys the currently contained value" as stated by the docu. As I read the standard, then after a v.emplace(...) the current value in the variant is always destroyed and the new type is either the set type or valueless.
I do get that the condition is_trivially_copyable excludes all types that have an observable destructor. So this can also be though as: "as-if variant is reinitialized with the old value" or so. But the state of the variant is an observable effect. So does the standard indeed allow, that emplace does not change the current value?
Edit in response to a standard quote:
Then initializes the contained value as if direct-non-list-initializing a value of type TI with the arguments std::forward<Args>(args)....
Does T tmp {std::forward<Args>(args)...}; this->value = std::move(tmp); really count as a valid implementation of the above? Is this what is meant by "as if"?
I think the important part of the standard is this:
From https://timsong-cpp.github.io/cppwp/n4659/variant.mod#12
23.7.3.4 Modifiers
(...)
template
variant_alternative_t>& emplace(Args&&... args);
(...) If an exception is thrown during the initialization of the contained value, the variant might not hold a value
It says "might" not "must". I would expect this to be intentional in order to allow implementations like the one used by gcc.
As you mentioned yourself, this is only possible if the destructors of all alternatives are trivial and thus unobservable because destroying the previous value is required.
Followup question:
Then initializes the contained value as if direct-non-list-initializing a value of type TI with the arguments std::forward<Args>(args)....
Does T tmp {std::forward(args)...}; this->value = std::move(tmp); really count as a valid implementation of the above? Is this what is meant by "as if"?
Yes, because for types that are trivially copyable there is no way to detect the difference, so the implementation behaves as if the value was initialized as described. This would not work if the type was not trivially copyable.
So does the standard indeed allow, that emplace does not change the
current value?
Yes. emplace shall provide the basic guarantee of no leaking (i.e., respecting object lifetime when construction and destruction produce observable side effects), but when possible, it is allowed to provide the strong guarantee (i.e., the original state is kept when an operation fails).
variant is required to behave similarly to a union — the alternatives are allocated in one region of suitably allocated storage. It is not allowed to allocate dynamic memory. Therefore, a type-changing emplace has no way to keep the original object without calling an additional move constructor — it has to destroy it and construct the new object in place of it. If this construction fails, then the variant has to go to the exceptional valueless state. This prevents weird things like destroying a nonexistent object.
However, for small trivially copyable types, it is possible to provide the strong guarantee without too much overhead (even a performance boost for avoiding a check, in this case). Therefore, the implementation does it. This is standard-conforming: the implementation still provides the basic guarantee as required by the standard, just in a more user-friendly way.
Edit in response to a standard quote:
Then initializes the contained value as if
direct-non-list-initializing a value of type TI with the arguments
std::forward<Args>(args)....
Does T tmp {std::forward<Args>(args)...}; this->value =
std::move(tmp); really count as a valid implementation of the above?
Is this what is meant by "as if"?
Yes, if the move assignment produces no observable effect, which is the case for trivially copyable types.
std::default_delete can be specialized to allow std::unique_ptrs to painlessly manage types which have to be destroyed by calling some custom destroy-function instead of using delete p;.
There are basically two ways to make sure an object is managed by a std::shared_ptr in C++:
Create it managed by a shared-pointer, using std::make_shared or std::allocate_shared. This is the preferred way, as it coalesces both memory-blocks needed (payload and reference-counts) into one. Though iff there are only std::weak_ptrs left, the need for the reference-counts will by necessity still pin down the memory for the payload too.
Assign management to a shared-pointer afterwards, using a constructor or .reset().
The second case, when not providing a custom deleter is interesting:
Specifically, it is defined to use its own deleter of unspecified type which uses delete [] p; or delete p; respectively, depending on the std::shared_ptr being instantiated for an Array or not.
Quote from n4659 (~C++17):
template<class Y> explicit shared_ptr(Y* p);
4 Requires: Y shall be a complete type. The expression delete[] p, when T is an array type, or delete p, when T is not an array type, shall have well-defined behavior, and shall not throw exceptions.
5 Effects: When T is not an Array type, constructs a shared_ptr object that owns the pointer p. Otherwise, constructs a shared_ptr that owns p and a deleter of an unspecified type that calls delete[] p. When T is not an array type, enables shared_from_this with p. If an exception is thrown, delete p is called when T is not an array type, delete[] p otherwise.
6 Postconditions: use_count() == 1 && get() == p.
[…]
template<class Y> void reset(Y* p);
3 Effects: Equivalent to shared_ptr(p).swap(*this).
My questions are:
Is there a, preferably good, reason that it is not specified to use std::default_delete instead?
Would any valid (and potentially useful?) code be broken by that change?
Is there already a proposal to do so?
Is there a, preferably good, reason that it is not specified to use std::default_delete instead?
Because it wouldn't do what you want. See, just because you can specialize something doesn't mean you can hijack it. The standard says ([namespace.std]):
A program may add a template specialization for any standard library template to namespace std only if the declaration depends on a user-defined type and the specialization meets the standard library requirements for the original template and is not explicitly prohibited.
The standard library requirement for std::default_delete<T>::operator()(T* ptr)'s behavior is that it "Calls delete on ptr." So your specialization of it must do the same.
As such, there should be no difference between having shared_ptr perform delete ptr; and having shared_ptr invoke default_delete<T>{}(ptr).
This is why unique_ptr takes a deleter type, rather than relying on you to specialize it.
From the comments:
The specialization deletes the object, in the only proper way.
But that's not what the requirement says. It says "Calls delete on ptr." It does not say something more ambiguous like "ends the lifetime of the object pointed to by ptr" or "destroys the object referenced by ptr". It gives explicit code that must happen.
And your specialization has to follow through.
If you remain unconvinced, the paper P0722R1 says this:
Note that the standard requires specializations of default_delete<T> to have the same effect as calling delete p;,
So clearly, the authors agree that specializing default_delete is not a mechanism for adding your own behavior.
So the premise of your question is invalid.
However, let's pretend for a moment that your question were valid, that such a specialization would work. Valid or not, specializing default_delete to customize deleter behavior is not the intended method of doing so. If it were the intent, you wouldn't need a deleter object for unique_ptr at all. At most, you would just need a parameter that tells you what the pointer type is, which would default to T*.
So that's a good reason not to do this.
I've checked a lot of move constructor/vector/noexcept threads, but I am still unsure what actually happens when things are supposed to go wrong. I can't produce an error when I expect to, so either my little test is wrong, or my understanding of the problem is wrong.
I am using a vector of a BufferTrio object, which defines a noexcept(false) move constructor, and deletes every other constructor/assignment operator so that there's nothing to fall back to:
BufferTrio(const BufferTrio&) = delete;
BufferTrio& operator=(const BufferTrio&) = delete;
BufferTrio& operator=(BufferTrio&& other) = delete;
BufferTrio(BufferTrio&& other) noexcept(false)
: vaoID(other.vaoID)
, vboID(other.vboID)
, eboID(other.eboID)
{
other.vaoID = 0;
other.vboID = 0;
other.eboID = 0;
}
Things compile and run, but from https://xinhuang.github.io/posts/2013-12-31-when-to-use-noexcept-and-when-to-not.html:
std::vector will use move when it needs to increase(or decrease) the capacity, as long as the move operation is noexcept.
Or from Optimized C++: Proven Techniques for Heightened Performance By Kurt Guntheroth:
If the move constructor and move assignment operator are not declared noexcept, std::vector uses the less efficient copy operations instead.
Since I've deleted those, my understanding is that something should be breaking here. But things are running ok with that vector. So I also created a basic loop that push_backs half a million times into a dummy vector, and then swapped that vector with another single-element dummy vector. Like so:
vector<BufferTrio> thing;
int n = 500000;
while (n--)
{
thing.push_back(BufferTrio());
}
vector<BufferTrio> thing2;
thing2.push_back(BufferTrio());
thing.swap(thing2);
cout << "Sizes are " << thing.size() << " and " << thing2.size() << endl;
cout << "Capacities are " << thing.capacity() << " and " << thing2.capacity() << endl;
Output:
Sizes are 1 and 500000
Capacities are 1 and 699913
Still no problems, so:
Should I see something going wrong, and if so, how can I demonstrate it?
A vector reallocation attempts to offer an exception guarantee, i.e. an attempt to preserve the original state if an exception is thrown during the reallocation operation. There are three scenarios:
The element type is nothrow_move_constructible: Reallocation can move elements which won't cause an exception. This is the efficient case.
The element type is CopyInsertable: if the type fails to be nothrow_move_constructible, this is sufficient to provide the strong guarantee, though copies are made during reallocation. This was the old C++03 default behaviour and is the less efficient fall-back.
The element type is neither CopyInsertable nor nothrow_move_constructible. As long as it is still move-constructible, like in your example, vector reallocation is possible, but does not provide any exception guarantees (e.g. you might lose elements if a move construction throws).
The normative wording that says this is spread out across the various reallocating functions. For example, [vector.modifiers]/push_back says:
If an exception is thrown while
inserting a single element at the end and T is CopyInsertable or is_nothrow_move_constructible_v<T> is true, there are no effects. Otherwise, if an exception is thrown by the move constructor of a
non-CopyInsertable T, the effects are unspecified.
I don't know what the authors of the posts you cite had in mind, though I can imagine that they are implicitly assuming that you want the strong exception guarantee, and so they'd like to steer you into cases (1) or (2).
There is nothing going wrong in your example. From std::vector::push_back:
If T's move constructor is not noexcept and T is not CopyInsertable into *this, vector will use the throwing move constructor. If it throws, the guarantee is waived and the effects are unspecified.
std::vector prefers non-throwing move constructors, and if none is available, will fall back on the copy constructor (throwing or not). But if that is also not available, then it has to use the throwing move constructor. Basically, the vector tries to save you from throwing constructors and leaving objects in an indeterminate state.
So in that regard, your example is correct, but if your move constructor actually threw an exception, then you'd have unspecified behavior.
TLDR So long as a type is MoveInsertable, you're okay, and here you're okay.
A type is MoveInsertable if, given the container's allocator A, an instance of the allocator assigned to variable m, a pointer to T* called p, and an r-value of type T the following expression is well-formed:
allocator_traits<A>::construct(m, p, rv);
For your std::vector<BufferTrio>, you are using the default std::allocator<BufferTrio>, so this call to construct calls
m.construct(p, std::forward<U>(rv))
Where U is a forwarding reference type (in your case it's BufferTrio&&, an rvalue reference)
So far so good,
m.construct will use placement-new to construct the member in-place([allocator.members])
::new((void *)p) U(std::forward<Args>(args)...)
At no point does this require noexcept. It's only for exception guarantee reasons.
[vector.modifiers] states that for void push_back(T&& x);
If an exception is thrown by the move
constructor of a non-CopyInsertable T, the effects are unspecified.
Finally,
Regarding your swap (Emphasis mine):
[container.requirements.general]
The expression a.swap(b), for containers a and b of a standard container type other than array, shall exchange
the values of a and b without invoking any move, copy, or swap operations on the individual container
elements.
Consider the following code.
using T = std::string;
void rotate_left(std::vector<T>& v) {
T temp = std::move(v[0]);
for (size_t i=0; i+1 < v.size(); ++i) {
v[i] = std::move(v[i+1]);
}
v.back() = std::move(temp);
}
int main()
{
std::vector<T> v(3); // a vector of three Ts
T x = std::move(v[1]); // move-from the second element
rotate_left(v);
// Can we now say that v[0] is in a moved-from state, or did we
// get undefined behavior when we moved from v[1] a second time?
}
The rotate_left function is just really simply shifting everything in the vector down by one position (and then putting the first element onto the end). My question is, does this function have defined behavior when one of the elements in the vector is in a "moved-from state"?
This is related but not quite the same as "self-move". In this case, we're moving from one moved-from object into another moved-from object, and my question is whether we can rely on this leaving both objects still in some moved-from state, or whether "assignment-from" is one of those operations that "has a precondition" and therefore can't be used on arbitrary moved-from objects.
I'm well aware that
this is perfectly fine for sane library types such as unique_ptr
this will be perfectly fine for my own user-defined types if I define them sanely
this will be problematic for my own user-defined types if I define them insanely
this is perfectly fine for all the library types I've tried on libstdc++ and libc++
So what I'm really looking for is either:
concrete wording from the Standard proving that this must be perfectly fine for all STL types going forward, or
a concrete example of an STL or Boost type "in the wild" where this code is definitely problematic
I infer from this bug that _GLIBCXX_DEBUG checks for self-move, but I can confirm that it does not check for move-from-moved; is this because they consider move-from-moved to be safe and legal, or just because nobody wrote the code to check for it yet?
Your program's behavior is well-defined, but unspecified. The difference is that v has a valid state, you just don't know what that state is without inspecting it.
This corner of the standard has been controversial, and the wording has been difficult to get right. But I believe the latest wording (C++17) is our best attempt so far, so I will quote from that (N4660).
Disclaimer, I actually linked to N4659 as N4660 is not publicly available. The difference is inconsequential.
From 20.5.5.15 Moved-from state of library types [lib.types.movedfrom]
1 Objects of types defined in the C++ standard library may be moved from (15.8). Move operations may be explicitly specified or implicitly generated. Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.
This paragraph is a blanket statement for all types defined by the std::lib that moved-from objects are not "poison", you just don't know value they have.
Furthermore, each algorithm (including member functions) defined in the standard may have a list of preconditions that must be true prior to calling that algorithm. If there are no preconditions listed, that means that you can always call that function.
Move assignment for all types defined in the std::lib never have any preconditions listed for the left or right hand side arguments.
It is hard to quote something that doesn't exist, but that is the way this specification works.
Generalizing further, the following section refers to all types (i.e. user-supplied) that are used with the std::lib:
20.5.3.1 Template argument requirements [utility.arg.requirements]
Table 23 — MoveConstructible requirements [moveconstructible]
T u = rv;
T(rv);
rv's state is unspecified in the post-condition.
Table 25 — MoveAssignable requirements [moveassignable]
t = rv
Only if t and rv do not refer to the same object, t is equivalent to the value of rv before the assignment.
Afterwards, rv's state is unspecified (whether or not t and rv refer to the same object).
Furthermore this sections notes (notes are non-normative and often the normative text appears elsewhere) that rv must still meet the requirements of the library component (algorithm) it is being used with, even though it has been moved from.
For example, std::sort is allowed to move from a value x, and then use that x in a comparison expression. x must be LessThanComparable, whether or not x is moved-from. Only x's value is unspecified.
bool b = x < x; // b must be false, no matter what!
I know that generally the standard places few requirements on the values which have been moved from:
N3485 17.6.5.15 [lib.types.movedfrom]/1:
Objects of types defined in the C++ standard library may be moved from (12.8). Move operations may
be explicitly specified or implicitly generated. Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.
I can't find anything about vector that explicitly excludes it from this paragraph. However, I can't come up with a sane implementation that would result in the vector being not empty.
Is there some standardese that entails this that I'm missing or is this similar to treating basic_string as a contiguous buffer in C++03?
I'm coming to this party late, and offering an additional answer because I do not believe any other answer at this time is completely correct.
Question:
Is a moved-from vector always empty?
Answer:
Usually, but no, not always.
The gory details:
vector has no standard-defined moved-from state like some types do (e.g. unique_ptr is specified to be equal to nullptr after being moved from). However the requirements for vector are such that there are not too many options.
The answer depends on whether we're talking about vector's move constructor or move assignment operator. In the latter case, the answer also depends on the vector's allocator.
vector<T, A>::vector(vector&& v)
This operation must have constant complexity. That means that there are no options but to steal resources from v to construct *this, leaving v in an empty state. This is true no matter what the allocator A is, nor what the type T is.
So for the move constructor, yes, the moved-from vector will always be empty. This is not directly specified, but falls out of the complexity requirement, and the fact that there is no other way to implement it.
vector<T, A>&
vector<T, A>::operator=(vector&& v)
This is considerably more complicated. There are 3 major cases:
One:
allocator_traits<A>::propagate_on_container_move_assignment::value == true
(propagate_on_container_move_assignment evaluates to true_type)
In this case the move assignment operator will destruct all elements in *this, deallocate capacity using the allocator from *this, move assign the allocators, and then transfer ownership of the memory buffer from v to *this. Except for the destruction of elements in *this, this is an O(1) complexity operation. And typically (e.g. in most but not all std::algorithms), the lhs of a move assignment has empty() == true prior to the move assignment.
Note: In C++11 the propagate_on_container_move_assignment for std::allocator is false_type, but this has been changed to true_type for C++1y (y == 4 we hope).
In case One, the moved-from vector will always be empty.
Two:
allocator_traits<A>::propagate_on_container_move_assignment::value == false
&& get_allocator() == v.get_allocator()
(propagate_on_container_move_assignment evaluates to false_type, and the two allocators compare equal)
In this case, the move assignment operator behaves just like case One, with the following exceptions:
The allocators are not move assigned.
The decision between this case and case Three happens at run time, and case Three requires more of T, and thus so does case Two, even though case Two doesn't actually execute those extra requirements on T.
In case Two, the moved-from vector will always be empty.
Three:
allocator_traits<A>::propagate_on_container_move_assignment::value == false
&& get_allocator() != v.get_allocator()
(propagate_on_container_move_assignment evaluates to false_type, and the two allocators do not compare equal)
In this case the implementation can not move assign the allocators, nor can it transfer any resources from v to *this (resources being the memory buffer). In this case, the only way to implement the move assignment operator is to effectively:
typedef move_iterator<iterator> Ip;
assign(Ip(v.begin()), Ip(v.end()));
That is, move each individual T from v to *this. The assign can reuse both capacity and size in *this if available. For example if *this has the same size as v the implementation can move assign each T from v to *this. This requires T to be MoveAssignable. Note that MoveAssignable does not require T to have a move assignment operator. A copy assignment operator will also suffice. MoveAssignable just means T has to be assignable from an rvalue T.
If the size of *this is not sufficient, then new T will have to be constructed in *this. This requires T to be MoveInsertable. For any sane allocator I can think of, MoveInsertable boils down to the same thing as MoveConstructible, which means constructible from an rvalue T (does not imply the existence of a move constructor for T).
In case Three, the moved-from vector will in general not be empty. It could be full of moved-from elements. If the elements don't have a move constructor, this could be equivalent to a copy assignment. However, there is nothing that mandates this. The implementor is free to do some extra work and execute v.clear() if he so desires, leaving v empty. I am not aware of any implementation doing so, nor am I aware of any motivation for an implementation to do so. But I don't see anything forbidding it.
David Rodríguez reports that GCC 4.8.1 calls v.clear() in this case, leaving v empty. libc++ does not, leaving v not empty. Both implementations are conforming.
While it might not be a sane implementation in the general case, a valid implementation of the move constructor/assignment is just copying the data from the source, leaving the source untouched. Additionally, for the case of assignment, move can be implemented as swap, and the moved-from container might contain the old value of the moved-to container.
Implementing move as copy can actually happen if you use polymorphic allocators, as we do, and the allocator is not deemed to be part of the value of the object (and thus, assignment never changes the actual allocator being used). In this context, a move operation can detect whether both the source and the destination use the same allocator. If they use the same allocator the move operation can just move the data from the source. If they use different allocators then the destination must copy the source container.
In a lot of situations, move-construction and move-assignment can be implemented by delegating to swap - especially if no allocators are involved. There are several reasons for doing that:
swap has to be implemented anyway
developer efficiency because less code has to be written
runtime efficiency because fewer operations are executed in total
Here is an example for move-assignment. In this case, the move-from vector will not be empty, if the moved-to vector was not empty.
auto operator=(vector&& rhs) -> vector&
{
if (/* allocator is neither move- nor swap-aware */) {
swap(rhs);
} else {
...
}
return *this;
}
I left comments to this effect on other answers, but had to rush off before fully explaining. The result of a moved-from vector must always be empty, or in the case of move assignment, must be either empty or the previous object's state (i.e. a swap), because otherwise the iterator invalidation rules cannot be met, namely that a move does not invalidate them. Consider:
std::vector<int> move;
std::vector<int>::iterator it;
{
std::vector<int> x(some_size);
it = x.begin();
move = std::move(x);
}
std::cout << *it;
Here you can see that iterator invalidation does expose the implementation of the move. The requirement for this code to be legal, specifically that the iterator remains valid, prevents the implementation from performing a copy, or small-object-storage or any similar thing. If a copy was made, then it would be invalidated when the optional is emptied, and the same is true if the vector uses some kind of SSO-based storage. Essentially, the only reasonable possible implementation is to swap pointers, or simply move them.
Kindly view the Standard quotes on requirements for all containers:
X u(rv)
X u = rv
post: u shall be equal to the value that rv had before this construction
a = rv
a shall be equal to the value that rv had before this assignment
Iterator validity is part of the value of a container. Although the Standard does not unambiguously state this directly, we can see in, for example,
begin() returns an iterator referring to the first element in the
container. end() returns an iterator which is the past-the-end value
for the container. If the container is empty, then begin() == end();
Any implementation which actually did move from the elements of the source instead of swapping the memory would be defective, so I suggest that any Standard wordings saying otherwise is a defect- not least of which because the Standard is not in fact very clear on this point. These quotes are from N3691.