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.
Related
I have a class exposing through it's interface add function:
void AddObject(Object *o);
Inside the class I maintain the objects in set<shared_ptr<Object>>.
Since I will create shared_ptr from the received pointer I thought to limit the function argument to only rvalue pointers so to make sure that the user will not delete the pointer I use. And so I'll change the function declaration to:
void AddObject(Object* &&o);
so a typical use will be:
AddObject(new Object())
preventing the user to accidentally delete pointer I hold.
I don't want to to use shared_ptr in the interface because the user is not familiar with shared_ptr.
Do you think my suggestion is a good idea?
I think this is a bad idea. I'm sure there is a reason why shared_ptr c-tor that gets a raw pointer is marked explicit instead of using r-value. In my eyes, It's better to teach the users once about smart pointers or at least teach them about using make_shared/make_unique (which are safer and, in the case of make_shared, more efficient, BTW).
BTW, why shared_ptr and not unique_ptr?
Also, why set? Even if you want to make sure you hold each pointer only once and searching a vector each time doesn't look natural enough in your code, I don't see a reason to hold the pointers sorted instead of using unordered_set.
First of all, this approach will not prevent the user from deleting the pointer. Consider this example
auto obj = new Object();
AddObject(std::move(obj));
delete obj;
Secondly, the amount of steps between calling new and the creation of shared_ptr should be as few as possible. If anything happens inside AddObject before it can create a shared_ptr, the object will never get deleted.
The same applies if there are more arguments to AddObject(). If constructing those fails, you will leak memory.
void AddObject(Object* &&o, SomeOtherObject* x);
AddObject(new Object(), xx()); // if xx() throws, memory leak will occur
Ideally you would "wrap" object creating into shared_ptr construction:
void AddObject(std::shared_ptr<Object> o);
AddObject(std::make_shared<Object>());
Either of the following methods may solve your problem.
You may append more comments for AddObject to tell users that delete the pointer they added is not allowed. This is almost enough.
Or, you could also make Object inherits from a base class which has a private destructor and a method named destroyByOwner.
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.
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.
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.
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