Object state after move semantics - c++11

I want to understand move semantics and rvalue reference and the object state after the function call.
For example: I expect that caller fills the list and gets to the constructor argument:
typedef std::list<int> IntList;
class IntHolder {
public:
explicit IntHolder(IntList&& l)
: m_h(l)
{}
private:
IntList m_h;
};
IntList a;
a.push_back(1);
IntHolder holder(a);
// ... is 'a' guaranteed empty (not empty) here?

a is guaranteed to not be empty, because it was never passed as an rvalue reference: it is a declared variable, not a compiler generated temporary, and hence cannot be passed as an rvalue reference.
What's happening here is that a temporary IntList gets copy-constructed from a, and that temporary is passed to your constructor. a itself remains intact.
To make things really complicated: even that temporary is not moved! When you use an rvalue reference, it decays to a normal reference, which happens when you say m_h(l) in your initializer list. If it were otherwise, you would not be able to access l from within your constructor. So, the temporary is copied a second time. You can enforce move semantics by replacing m_h(l) with m_h(std::move(l)).
Whenever you try to invoke move semantics, what happens depends on how the class you are using is written. Thus, there are no language guarantees. The only thing a move constructor needs to make sure is that the destructor will not mess up when it is run. It is simply an error to access an object in any way after invoking move semantics on it.

Related

Am getting cannot bind non-const lvalue reference of type ‘Type&’ to an rvalue of type 'Type'

Here's my class
class DriverPoint
{
public:
DriverPoint(){};
DriverPoint (DriverPoint& dp) = default;
DriverPoint(double lat, double lon)
{
_lat = lat;
_lon = lon;
}
double _lat;
double _lon;
};
// main
DriverPoint driverPoint(lat, _long);
vector.push_back(driverPoint);
When i try to compile it am getting
cannot bind non-const lvalue reference of type ‘DriverPoint&’ to an rvalue of type ‘DriverPoint’
When adding an element to std containers it is copied (or moved, in case of an rvalue).
So the call to: vec.push_back(driverPoint) calls internally the copy constructor of DriverPoint.
There are two overloads for vector::push_back
(1) void push_back( const T& value );
(2) void push_back( T&& value );
(1) The new element is initialized as a copy of value.
(2) value is moved into the new element.
Overload 2 is for rvalue reference (so it requires T to be MoveInsertable which in this case means having a move constructor). Note that it is not a forwarding reference here, since T is a template parameter of the class and not of the function.
Your call to push_back goes to the first overload, as the passed parameter (the object driverPoint) is a non-const lvalue which can bind to const lvalue (the parameter const T& expected in overload 1 of push_back). Wait... the copy constructor is not called yet! But, inside vector::push_back there is an attempt to create a copy of T, by sending it to the copy constructor as const, which fails if the copy constructor is missing the 'const' on its parameter.
Step 1 - fixing the copy ctor
Adding of course 'const' to the parameter of the copy constructor would fix this: DriverPoint(const DriverPoint& driverPoint)
However, above fix is not the correct one (wait, it is correct, if you want a copy constructor make sure the parameter you expect is const lvalue ref - but in this case the copy constructor is not required at all...).
Step 2 - removing the copy ctor
The problem with having a copy constructor that is not required is that you are implicitly deleting the default move constructor (and others, following the rule of 5).
Note that the current code doesn't even allow adding an rvalue into the vector: vec.push_back(DriverPoint{}) would also fail with the same compilation error.
If you would fix the copy constructor to: DriverPoint(const DriverPoint& driverPoint) both adding lvalue and adding rvalue to the vector by calling push_back would work, but both would go through the copy ctor and not through move, as you didn't implement move and the default move is implicitly deleted if you declare any single one of the five: destructor, copy ctor, copy assignment, move ctor or move assignment.
If you wish to add copy ctor, e.g. for debugging, but still to preserve the default move operations, add:
DriverPoint(DriverPoint&&) = default; // move ctor
DriverPoint& operator=(DriverPoint&&) = default; // move assignment
Rule of Zero
The best way to go with types that do not require special care for copying or destruction, i.e. do not require user implementation of any of the three: destructor - copy ctor - copy assignment operator, is to go with the Rule of Zero -- do not declare any of the five - which would give you the defaults for all five.
Note that it is a common mistake in actual production code -- declaring a destructor, a copy constructor or an assignment operator, thus implicitly deleting the move operations. This may hurt performance badly in quite a quiet way. Move may be used in many places without the programmer's notice, e.g. by std containers. And if it is implicitly deleted the inner fields of the type (e.g. strings, vectors, etc.) would always be copied, even when can be moved. It is especially common in code base that started before C++11, when move operations were not introduced yet but can be also found in new code.

move semantics with temps allocated with new

I'm just wondering if move semantics are restricted to syntax style B.
More specifically, with style B the object is created on the stack and moved. With style A, the object is created on the heap, but it seems can't be moved.
The question very specifically is, can you use move semantics such that the temp is created with NEW? If so, how?
//move c'tor
A(A&& other) : num(other.num), s(other.s){
other.num = 0;
other.s = nullptr; //dyn alloc obj
}
If you do this, it doesn't work (syntax style A).
A a2(new A("blah")); //error
A a2(move(new A("blah"))); //error
This is ok (syntax style B)
A a2(A("blah")); //uses c'tor once
A a2(move(A("blah"))) //uses c'tor followed by move c'tor
You seem to be confused about a number of things, so I'll try to shed some light. You probably know most of this already.
Move semantics were designed to allow objects to transfer ownership of their data to another object. This was motivated largely to avoid copying from temporary objects that didn't need it.
The move constructor is much like the copy constructor, A(const A&), but only accepts a non-const xvalue of the same type, A(A&&) (think of it as an expiring value). Therefore, this constructor can be invoked when given something like a return value from a function or a variable moved via std::move.
Now, the function std::move in itself is a bit of a misnomer, it doesn't actually do anything. All it does it cast a T or T& into a T&&.
To address your question directly, nothing I've mentioned is specific to where the moving object is located, but it is specific about the types. A("blah") calls the constructor with automatic memory and returns that A. new A("blah") on the other-hand calls the constructor with dynamic memory and returns a pointer to that A, ergo an A*. So your syntax A is not trying to invoke the move constructor, but another constructor like A(A*).
To move from an object referenced by a pointer, all you need to do is derefence and move:
A* a1 = new A("blah");
A a2(std::move(*a1));
There's nothing stopping you from defining a constructor like A(A*), but that shouldn't be needed and isn't recommended for using move semantics.
Also, your syntax B comments are incorrect; both are a value-constructor A("blah") followed by the move-constructor A(A&&); the std::move doesn't add anything here.

Overloading for pass-by-value and pass-by-rvalue-reference

I have two overloads of a subroutine that takes an argument of a type that occupies several Megabytes of dynamic memory and has a move constructor and assignment operator:
// Version intended for use when we the caller has
// deliberately passed an rvalue reference using std::move
void MyClass::setParameter(MyMoveableType &&newParameter)
{
m_theLocalParameter = std::move(newParameter);
}
// Version intended for use when the caller has passed
// some other type of value which shouldn't be moved
void MyClass::setParameter(MyMoveableType newParameter)
{
m_theLocalParameter = std::move(newParameter);
}
The intention is clearly that the first overload moves the contents of newParameter from wherever up the chain of subroutine-calls the newParameter object originated, whilst the second overload creates a brand new copy of newParameter (or invokes copy elision to avoid doing so where appropriate, such as where the argument is actually the return value from a function) and then moves the copy into the local data member, thus avoiding a further copy.
However, if I try actually to move an object into my class using the first overload:
{
MyClass theDestination;
MyMoveableType theObject
...
// ...Various actions which populate theObject...
...
TheDestination.setParameter(std::move(theObject));
...
}
...then on every compiler I've tried I get an error along the lines of:
call to member function 'setParameter' is ambiguous
Now I can see that passing an rvalue reference to the second overload would in fact be perfectly legal, and is what I'd expect the compiler to do, without giving a warning, if I hadn't provided the first overload. Even so, I'd expect it to be perfectly clear to the compiler what the intent of this code is, and therefore I'd expect that it would select the second overload as being the best match.
I can eliminate the error by redefining the second constructor to take a const reference and do away with the std::move (though it wouldn't be an error to
leave it in; the compiler would just ignore it). This would work all right, but I'd lose the opportunity to take advantage of copy elision. This could be
significant in performance terms for this particular application; the objects under discussion are high-resolution video frames streaming through at 30
frames per second.
Is there anything I can do under this circumstance to disambiguate the overloads and so have both a pass-by-value and pass-by-rvalue-reference version of my routine?
The intention is clearly that the first overload moves the contents of newParameter from wherever up the chain of subroutine-calls the newParameter object originated, whilst the second overload creates a brand new copy
Which is not really how you do it. You have two sane options:
Approach A
You write just the value overload and then move from it anyway - that means you'll always pay a constructor price, either move or copy.
Approach B
You write overloads for (const T&) and (T&&). That way you copy in the first one and skip the move CTOR in the second one using perfect forwarding.
I recommend approach A as a default, and B only when the c-tor call actually matters that much.

Is value returned by std::unique_ptr::get valid after moving unique_ptr?

Consider the following code snippet:
class Owner {
public:
Owner(std::unique_ptr<int> ptr) : owned_pointer<int>(std:move(ptr)) {}
private:
std::unique_ptr<int> owned_pointer;
};
std::unique_ptr<int> ptr(new int);
int* ptr1 = ptr.get();
Owner new_owner(std::move(ptr));
Is it safe to assume that ptr1 is valid as long as new_owner stays in scope? It seems to work, but I can't find a specification that states that explicitly - is it undefined behavior/implementation specific and just happen to work for me, or the code posted above is valid (ptr1 is guaranteed to point to moved pointer as long as it stays alive)?
Yes, the C++11 specification guarantees that transferring ownership of an object from one unique_ptr to another unique_ptr does not change the location of the object itself, and that get() on the second unique_ptr returns the same as it would have on the first unique_ptr before the transfer.
Looking at N3337, section 20.7.1:
Additionally, u can, upon request, transfer ownership to another unique pointer u2. Upon completion of such a transfer, the following
postconditions hold:
u2.p is equal to the pre-transfer u.p,
u.p is equal to nullptr, and
if the pre-transfer u.d maintained state, such state has been transferred to u2.d.
where u is a unique_ptr object that stores a pointer u.p.
The first bullet answers the question directly, since get() is specified as returning the u.p.
Yes, it is valid.
You can have multiple (plain) pointers pointing to the same object. The question is how long those pointers are valid or when the object pointed to is deleted.
A unique_ptr stores one more plain pointer and takes ownership, meaning it is responsible for when the object is destroyed. Moving it to another unique_ptr just transfers ownership, the object itself is still the same and all plain pointers pointing to it remain valid.
Only when the ownership is not transferred (or released) and the owning unique_ptr is destroyed, it also destroys the object. This would be the moment where all plain pointers pointing to the object become dangling pointers and dereferencing them would be illegal.

State of object after std::move construction

Is it legal/proper c++0x to leave an object moved for the purpose of move-construction in a state that can only be destroyed? For instance:
class move_constructible {...};
int main()
{
move_constructible x;
move_constructible y(std::move(x));
// From now on, x can only be destroyed. Any other method will result
// in a fatal error.
}
For the record, I'm trying to wrap in a c++ class a c struct with a pointer member which is always supposed to be pointing to some allocated memory area. All the c library API relies on this assumption. But this requirement prevents to write a truly cheap move constructor, since in order for x to remain a valid object after the move it will need its own allocated memory area. I've written the destructor in such a way that it will first check for NULL pointer before calling the corresponding cleanup function from the c API, so that at least the struct can be safely destroyed after the move.
Yes, the language allows this. In fact it was one of the purposes of move semantics. It is however your responsibility to ensure that no other methods get called and/or provide proper diagnostics. Note, usually you can also use at least the assignment operator to "revive" your variable, such as in the classical example of swapping two values.
See also this question

Resources