Why doesn't rvalue destroy right after it's used? - c++

I wrote the following program and expected that the rvalue gotten from std::move() would be destroyed right after it's used in the function call:
struct A
{
A(){ }
A(const A&){ std::cout << "A&" << std::endl; }
~A(){ std::cout << "~A()" << std::endl; }
A operator=(const A&){ std::cout << "operator=" << std::endl; return A();}
};
void foo(const A&&){ std::cout << "foo()" << std::endl; }
int main(){
const A& a = A();
foo(std::move(a)); //after evaluation the full-expression
//rvalue should have been destroyed
std::cout << "before ending the program" << std::endl;
}
But it was not. The following output was produced instead:
foo()
before ending the program
~A()
DEMO
As said in the answer
rvalues denote temporary objects which are destroyed at the next
semicolon
What did I get wrong?

std::move does not make a into a temporary value. Rather it creates an rvalue reference to a, which is used in function foo. In this case std::move is not doing anything for you.
The point of std::move is that you can indicate that a move constructor should be used instead of a copy constructor, or that a function being called is free to modify the object in a destructive way. It doesn't automatically cause your object to be destructed.
So what std::move does here is that if it wanted to, the function foo could modify a in a destructive way (since it takes an rvalue reference as its argument). But a is still an lvalue. Only the reference is an rvalue.
There's a great reference here that explains rvalue references in detail, perhaps that will clear a few things up.

Remember: std::move doesn't move the object. std::move is a simple cast that takes an lvalue and makes it look like an rvalue
foo, by taking an argument by rvalue reference, says that the input object will be modified, but left in a valid state. Nothing here about destroying the object.
In the end, a remains an lvalue, no matter how much you try to cast it.

std::move(a) does not alter a to become an rvalue.
Instead, it creates an rvalue reference to a.
Edit:
Note that, with your line
const A& a = A();
you rely on a the special case of local const references prolonging the lives of temporaries (see e.g. http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/). This feature predates C++11. As temporaries are basically rvalues, I understand now where your confusion comes from.
Note that by prolonging the life of the temporary (by means of assigning it to the local const reference), your object referenced by a cannot be classified as "object(s) which are destroyed at the next semicolon". Rather, it lives as long as the reference.
May someone else locate http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/ in the C++11 standard. Same for your sentence "rvalues denote temporary objects which are destroyed at the next semicolon".

The rvalue you get from std::move is an rvalue reference, and references don't have a destructor. You can't get to that reference anymore. So why don't you think it has been destroyed?

You didn't get anything wrong; rather, it is the committee that made a mistake in giving std::move that name. It's just a cast, making it easier to "select" move constructors and move assignment operators that actually perform moves. In this case you have neither, so the std::move does literally nothing. You're seeing the original object being destroyed when it goes out of scope as usual.

Related

When should we declare rvalue refs if they actually need std::move?

As many other posts explain, declaring one variable as an rvalue reference does not guarantee that it will call the move assignment operator, only using std::move (that is, casting to that same rvalue reference) will do it. For example:
struct A
{
A& operator=(A&& l_other) { std::cout << "move" << std::endl; }
A& operator=(A& l_other) { std::cout << "copy" << std::endl; }
};
A a;
// does not print anything, compiler does nothing here!
A&& b = std::move(a);
// prints "copy", not "move"
a = b;
// prints "move", but needs std::move()
a = std::move(b);
Seems that defining b as A&& does not move anything in that moment. After that, a = b does not move b automatically, even if right part is defined as A&&, as other posts explain. We need to explicitly call std::move(b) to move b.
My question is, what's the utility of defining a variable as A&&? I could perfectly define it as A& and move it exactly the same.
My question is, what's the utility of defining a variable as A&&? I could perfectly define it as A& and move it exactly the same.
If a named variables is passed into a function and modified like that without warning it's very displeasing, and can lead to unpleasant debugging sessions.
When you take by rvalue reference the type system enforces useful guarantees. Either the reference is bound to an rvalue, and so modifying it isn't going to violate anyone's assumptions (it's gone soon anyway). Or you were given explicit permission to move out of it. The calling context can't accidentally give you permission to modify an lvalue, it must do so with an explicit cast (std::move).
Explicit is better than implicit. And having rvalue references interact with the type system the way they do helps make sure moving does not leave objects in an unexpected state. A glance at the calling context reveals that the variable passed into the function may be left with an unknown state, because it's explicitly surrounded with a call to std::move.
That's the benefit of rvalue references.
Think of it like this: There are two parties involved: a producer of an object (to be bound by the reference) and a consumer of the reference.
The kind of reference defines what is allowed to go into the reference (producer), not what is in the variable (consumer).
For the consumer, it makes no difference which kind of reference it is.
For the producer, an rvalue reference is a sign that only an expendable value can be bound (a temporary or an xvalue such as the result of str::move).

What exactly happens when we use rvalue references and how does std::move work?

I am trying to understand rvalue reference and move semantics. In following code, when I pass 10 to Print function it calls rvalue reference overload, which is expected. But what exactly happens, where will that 10 get copied (or from where it referred). Secondly what does std::move actually do? Does it extract value 10 from i and then pass it? Or it is instruction to compiler to use rvalue reference?
void Print(int& i)
{
cout<<"L Value reference "<<endl;
}
void Print(int&& i)
{
cout<<"R Value reference "<< endl;
}
int main()
{
int i = 10;
Print(i); //OK, understandable
Print(10); //will 10 is not getting copied? So where it will stored
Print(std::move(i)); //what does move exactly do
return 0;
}
Thanks.
In the case of a 10, there will probably be optimisations involved which will change the actual implementation, but conceptually, the following happens:
A temporary int is created and initialised with the value 10.
That temporary int is bound to the r-value reference function parameter.
So conceptually, there's no copying - the reference will refer to the temporary.
As for std::move(): there may be some tricky bits related to references etc., but principally, it's just a cast to r-value reference. std::move() does not actually move anything. It just turns its argument into an r-value, so that it can be moved from.
"Moving" is not really a defined operation, anyway. While it's convenient to think about moving, the important thing is l-value vs. r-value distinction.
"Moving" is normally implemented by move constructors, move assignment operators and functions taking r-value references (such as push_back()). It is their implementation that makes the move an actual move - that is, they are implemented so that they can "steal" the r-value's resources instead of copying them. That's because, being an r-value, it will no longer be accessible (or so you promise the compiler).
That's why std::move() enables "moving" - it turns its argument into an r-value, signalling, "hey, compiler, I will not be using this l-value any more, you can let functions (such as move ctors) treat it as an r-value and steal from it."
But what exactly happens, where that 10 will get copied (or from where it referred)
A temporary value is created, and a reference passed to the function. Temporaries are rvalues, so can be bound to rvalue references; so the second overload is chosen.
Secondly what std::move actually do?
It gives you an rvalue reference to its argument. It's equivalent (by definition) to static_cast<T&&>.
Despite the name, it doesn't do any movement itself; it just gives you a reference that can be used to move the value.
std::move cast int in int&& via static_cast<int&&>.
Eventually if the type is a class or a struct, the move constructor if it is defined (implicitly or explicitly) will be invoked instead of the copy constructor/classical constructor.

Do built-in types have move semantics?

Consider this code:
#include <iostream>
using namespace std;
void Func(int&& i) {
++i;
}
int main() {
int num = 1234;
cout << "Before: " << num << endl;
Func(std::move(num));
cout << "After: " << num << endl;
}
Its output is:
Before: 1234
After: 1235
Clearly, i is being modified inside Func, as it is bound to parameter i after being "converted" to an r-value reference by std::move.
Well, my point:
Moving an object means transferring ownership of resources from one object into another. However, built-in types holds no resources because they themselves are the resources. It makes no sense to transfer the resources they hold. As shown by the example, num's value is modified. Its resource, its self, is the one being modified.
Do built-in types have move semantics?
Also, Do built-in type objects after it is moved (if it is) a well-defined behavior?
And so, is the one shown by the example a well-defined behavior?
Yes, the behaviour shown in the example is the only behaviour allowed by the standard. That is because std::move doesn't move. The things that move are move constructors and move assignment operators.
All std::move does is change an lvalue into an xvalue, so that it can bind to rvalue references. It does not invoke any constructor or anything else. Changing the value category happens at the type level. Nothing happens at runtime.
Rvalue references are still references: they refer to the original object. The function increments the original integer through the reference given.
If a function takes an argument by reference, no copies nor moves happen: the original object is bound to the reference.
If a function takes an argument by value, then we might have a move.
However, fundamental types don't have move constructors. Moves degrade to copies in that case.

C++ - temporary variables and their lifetime

This question can be considered a follow-up to the following question: C++ temporary variable lifetime.
Qt containers support the stream-like initialization syntax. Now, when I write the following code, my QVector is destructed right after assignment and the reference becomes dangling.
const QVector<QString>& v = QVector<QString>() << "X" << "Y" << "Z";
Corresponding operator<< is implemented the following way:
inline QVector<T> &operator<< (const T &t)
{ append(t); return *this; }
As far as I know, 10.4.10 Temporary Objects states that the lifetime of a temporary object is extended to match the lifetime of the correspnding const reference to it.
However, in this case the temporary object QVector<QString>() is destructed earlier.
I guess that probably this happens due to the fact that the last operation returns a QVector<QString>& and shouldn't know anything about the lifetime of the temporary QVector<QString>, but this explanation isn't strict and might be wrong.
So, why does this happen?
The lifetime of a temporary is only extended if it is bound to a const-reference:
const QVector<QString>& v = QVector<QString>();
However, in your code you are not binding the temporary to anything. Rather, you're calling a member function (of the temporary), which returns a reference (to the temporary). The result of this function call is no longer a temporary object, but just a plain reference. The original temporary object expires at the end of the full expression in which it appears, and the reference v becomes dangling.
(In the new C++, it is possible to prohibit such "accidents" by virtue of rvalue-qualified member functions, i.e. you could =delete the rvalue version of the << operator.)

Global const string& smells bad to me, is it truly safe?

I'm reviewing a collegue's code, and I see he has several constants defined in the global scope as:
const string& SomeConstant = "This is some constant text";
Personally, this smells bad to me because the reference is referring to what I'm assuming is an "anonymous" object constructed from the given char array.
Syntactically, it's legal (at least in VC++ 7), and it seems to run, but really I'd rather have him remove the & so there's no ambiguity as to what it's doing.
So, is this TRULY safe and legal and I'm obsessing? Does the temp object being constructed have a guaranteed lifetime? I had always assumed anonymous objects used in this manner were destructed after use...
So my question could also be generalized to anonymous object lifetime. Does the standard dictate the lifetime of an anonymous object? Would it have the same lifetime as any other object in that same scope? Or is it only given the lifetime of the expression?
Also, when doing it as a local, it's obviously scoped differently:
class A
{
string _str;
public:
A(const string& str) :
_str(str)
{
cout << "Constructing A(" << _str << ")" << endl;
}
~A()
{
cout << "Destructing A(" << _str << ")" << endl;
}
};
void TestFun()
{
A("Outer");
cout << "Hi" << endl;
}
Shows:
Constructing A(Outer);
Destructing A(Outer);
Hi
It's completely legal. It will not be destructed until the program ends.
EDIT: Yes, it's guaranteed:
"All objects which do not have dynamic
storage duration, do not have thread
storage duration, and are not local
have static storage duration. The
storage for these objects shall last
for the duration of the program
(3.6.2, 3.6.3)."
-- 2008 Working Draft, Standard for Programming Language C++, § 3.7.1 p. 63
As Martin noted, this is not the whole answer. The standard draft further notes (§ 12.2, p. 250-1):
"Temporaries of class type are created
in various contexts: binding an rvalue
to a reference (8.5.3) [...] Even when
the creation of the temporary object
is avoided (12.8), all the semantic
restrictions shall be respected as if
the temporary object had been created.
[...] Temporary objects are destroyed
as the last step in evaluating the
full-expression (1.9) that (lexically)
contains the point where they were
created. [...] There are two contexts
in which temporaries are destroyed at
a different point than the end of the
full-expression. [...] The second
context is when a reference is bound
to a temporary. The temporary to which
the reference is bound or the
temporary that is the complete object
of a subobject to which the reference
is bound persists for the lifetime of
the reference except as specified
below."
I tested in g++ if that makes you feel any better. ;)
Yes it is valid and legal.
const string& SomeConstant = "This is some constant text";
// Is equivalent too:
const string& SomeConstant = std::string("This is some constant text");
Thus you are creating a temporary object.
This temporary object is bound to a const& and thus has its lifetime extended to the lifespan of the variable it is bound too (ie longer than the expression in which it was created).
This is guranteed by the standard.
Note:
Though it is legal. I would not use it. The easist solution would be to convert it into a const std::string.
Usage:
In this situation because the variable is in global scope it is valid for the full length of the program. So it can be used as soon as execution enters main() and should not be accessed after executiuon exits main().
Though it technically may be avilable before this your usage of it in constructors/destructors of global objects should be tempered with the known problem of global variable initialization order.
Extra Thoughts:
This on the other hand will not suffer from the problem:
char const* SomeConstant = "This is some constant text";
And can be used at any point. Just a thought.
It might be legal, but still ugly. Leave out the reference !
const string SomeConstant = "This is some constant text";
It's as legal as it's ugly.
It's legal to extend a temporary variable with a const reference, this is used by Alexandrescu's ScopeGaurd see this excellent explanation by Herb Sutter called A candidate for the "Most important const".
That being said this specific case is an abuse of this feature of C++ and the reference should be removed leaving a plain const string.
By declaring it as const (which means it can't be changed) and then making it a reference, which implies that someone might change it, seems like bad form, at the very least. Plus, as I am sure you understand, global variables are BAD, and rarely necessary.
Okay, folks correct me if I'm off the deep end, but here's my conclusions listening to all of your excellent responses:
A) it is syntactically and logically legal, the & extends the lifetime of the temp/anonymous from beyond expression level to the life of the reference. I verified this in VC++7 with:
class A {
public: A() { cout << "constructing A" << endl; }
public: ~A() { cout << "destructing A" << endl; }
};
void Foo()
{
A();
cout << "Foo" << endl;
}
void Bar()
{
const A& someA = A();
cout << "Bar" << endl;
}
int main()
{
Foo(); // outputs constructing A, destructing A, Foo
Bar(); // outputs constructing A, Bar, destructing A
return 0;
}
B) Though it is legal, it can lead to some confusion as to the actual lifetime and the reference in these cases give you no benefit of declaring it as a non-reference, thus the reference should probably be avoided and may even be extra space. Since there's no benefit to it, it's unnecessary obfuscation.
Thanks for all the answers it was a very interesting dicussion. So the long and short of it: Yes, it's syntactically legal, no it's not technically dangerous as the lifetime is extended, but it adds nothing and may add cost and confusion, so why bother.
Sound right?