Why would a std::move of std::shared_ptr casue destruction - c++

I got some code look like this
struct A
{
int i;
A(int i) : i(i) {}
~A()
{
cout << "destroy " << i << endl;
}
};
using p = shared_ptr<A>;
p f(int i)
{
return make_shared<A>(i);
}
int main()
{
auto i = f(1);
cout << "a" << endl;
p && j = f(2);
cout << "a" << endl;
p && k = move(f(3));
cout << "a" << endl;
auto l = move(f(4));
cout << "a" << endl;
p ptr = make_shared<A>(5);
auto && a = move(ptr);
cout << "a" << endl;
}
and the output is
a
a
destroy 3
a
a
a
destroy 5
destroy 4
destroy 2
destroy 1
I don't understand why move a function's return value to a rvalue reference cause destruction. But put it directly to a rvalue reference is ok.
The problem is actually found with std::get<> of a std::tuple. I have a function that return a tuple of two shared_ptr and use std::get<> to access the element. But I found that auto && i = get<0>(f()) will cause error but auto i = get<0>(f()) won't. Then I find a similar but simpler situation for std::move

p && j = f(2);
Here, f returns a prvalue of type std::shared_ptr<A>. When you bind a reference to a prvalue it extends the lifetime of the prvalue to be that of the reference. That means that the object returned by f will have the same lifetime as j.
p && k = move(f(3));
Here, once again f returns a prvalue. std::move's parameter binds to that prvalue, extending its lifetime to the lifetime of the parameter. std::move does not return a prvalue though. It returns an xvalue. Lifetime extension does not apply to xvalues, so the object returned by f gets destroyed as soon as std::move's parameter's lifetime ends. That is, it gets destroyed when std::move returns. k then becomes a dangling reference.

Let’s go through these one by one:
auto i = f(1); // (1)
This creates a local variable i (not a reference) initialized with the return value of f(1). This is OK. The lifetime of i is until the end of the block.
p && j = f(2); // (2)
This initializes the reference j with a reference to the object returned by f(2), and this extends the lifetime of the value returned by f(2), so this is OK. The lifetime of j is until the end of the block.
p && k = move(f(3)); // (3)
This initializes the reference with a reference to the object returned by move, no lifetime extension occurs since the value returned by move is a reference to the temporary returned by f(2) (and not a temporary object), but the temporary returned by f(2) dies as soon as k is initialized, since its lifetime is not extended (it’s not assigned to a reference variable), and k ends up being a dangling reference.
auto l = move(f(4)); // (4)
Same as (1), the move is useless. The lifetime of l is until the end of the block.
p ptr = make_shared<A>(5); // (5)
This is a local variable initialization. The lifetime of ptr is until the end of the block.
auto && a = move(ptr);
a is a reference to ptr. This does not change the lifetime of ptr.

Because by using std::move you've turned the return value into a reference. And not a const reference, so it's a temporary.
So no copy takes place and as soon as the line ends, the return value is destroyed.

Related

Reference initialization - temporary bound to return value

In an article about reference initialization at cppreference.com (Lifetime of a temporary), it says:
a temporary bound to a return value of a function in a return statement is not extended: it is destroyed immediately at the end of the return expression. Such function always returns a dangling reference.
This excerpt addresses the exceptions of extending the lifetime of a temporary by binding a reference to it. What do they actually mean by that? I've thought about something like
#include <iostream>
int&& func()
{
return 42;
}
int main()
{
int&& foo = func();
std::cout << foo << std::endl;
return 0;
}
So foo should be referencing the temporary 42. According to the excerpt, this should be a dangling reference - but this prints 42 instead of some random value, so it works perfectly fine.
I'm sure I'm getting something wrong here, and would appreciate if somebody could resolve my confusion.
Your example is very good, but your compiler is not.
A temporary is often a literal value, a function return value, but also an object passed to a function using the syntax "class_name(constructor_arguments)". For example, before lambda expressions were introduced to C++, to sort things one would define some struct X with an overloaded operator() and then make a call like this:
std::sort(v.begin(), v.end(), X());
In this case you expect that the lifetime of the temporary constructed with X() will end on the semicolon that ends the instruction.
If you call a function that expects a const reference, say, void f(const int & n), with a temporery, e.g. f(2), the compiler creates a temporary int, initailses it with 2, and passes a reference to this temporary to the function. You expect this temporary to end its life with the semicolon in f(2);.
Now consider this:
int && ref = 2;
std::cout << ref;
This code is perfectly valid. Notice, however, that here the compiler also creates a temporary object of type int and initalises it with 2. This is this temporary that ref binds to. However, if the temporary's lifetime was limited to the instruction it is created within, and ended on the semicolon that marks the end of instruction, the next instruction would be a disaster, as cout would be using a dangling reference. Thus, references to temporaries like the one above would be rather impractical. This is what the "extension of the lifetime of a temporary" is needed for. I suspect that the compiler, upon seeing something like int && ref = 2 is allowed to transform it to something like this
int tmp = 2;
int && ref = std::move(tmp);
std::cout << ref; // equivalent to std::cout << tmp;
Without lifetime expansion, this could look rather like this:
{
int tmp = 2;
int && ref = std::move(tmp);
}
std::cout << ref; // what is ref?
Doing such a trick in a return statement would be pointless. There's no reasonable, safe way to extend the lifetime of any object local to a function.
BTW. Most modern compilers issue a warning and reduce your function
int&& func()
{
return 42;
}
to
int&& func()
{
return nullptr;
}
with an immediate segfault upon any attempt to dereference the return value.

Returning const reference to temporary behaves differently than local const reference?

I'm trying to get a better grasp of how lvalues and rvalues are dealt with as references, so I created this toy example:
#include <iostream>
struct Val
{
Val(int num) : num(num){};
~Val()
{
std::cout << "Destructing with value " << num << std::endl;
}
int num;
};
const Val &test(const Val &val)
{
return val;
}
int main()
{
std::cout<< "Creating foo with value 5" <<std::endl;
const Val &foo = test(Val(5));
std::cout<< "Creating bar with value 3" <<std::endl;
const Val &bar(3);
std::cout<< "Finishing main function" <<std::endl;
return 0;
}
This prints out:
Creating foo with value 5
Destructing with value 5
Creating bar with value 3
Finishing main function
Destructing with value 3
Essentially we're seeing this rvalue Val(5) bind to const reference parameter val in function test, and that same value returned — however, the destructor gets called immediately as it's a temporary. But when we try constructing Val(3) and assigning to a const reference, it remains in scope for the entire block.
I was under the conception that we can bind rvalues to const references and that'll extend their lifetime until that reference goes out of scope, but that seems to not necessarily be the case here. I'd appreciate any insight into where I'm misunderstanding.
Given const Val &foo = test(Val(5));, the temporary Val(5) will be destroyed after the full expression immediately, its lifetime won't be extended to the lifteime of the reference foo. It's not bound to foo directly, but bound to the reference parameter of test.
In reference initialization,
(emphasis mine)
Whenever a reference is bound to a temporary or to a subobject
thereof, the lifetime of the temporary is extended to match the
lifetime of the reference, with the following exceptions:
a temporary bound to a reference parameter in a function call exists until the end of the full expression containing that function call: if
the function returns a reference, which outlives the full expression,
it becomes a dangling reference.
In general, the lifetime of a temporary cannot be further extended by
"passing it on": a second reference, initialized from the reference to
which the temporary was bound, does not affect its lifetime.
When you add an output to test() itself
const Val &test(const Val &val)
{
std::cout << "test with value " << val.num << '\n';
return val;
}
You will see, that the temporary lives until the end of the function, but not beyond
Creating foo with value 5
test with value 5
Destructing with value 5
Creating bar with value 3
Finishing main function
Destructing with value 3
I was under the conception that we can bind rvalues to const references and that'll extend their lifetime until that reference goes out of scope
Yes, indeed val parameter does extend the lifetime of Val(5), but when test returns, val itself is destroyed, and nothing keeps Val(5) alive anymore, so it is destroyed too.
And by returning by reference you are actually returning a dangling reference, so you have undefined behavior: the fact that the parameter val is a reference to the actual argument Val(5) does not influence the fact that val is no more available after test returns, and you are returning (well, trying to return) it (val) by reference, not its referenced entity.

Returning an rvalue reference that is passed to a function as an argument

I want to understand whether I can safely return an rvalue reference that is passed as an argument to a function and it doesn't get destroyed with the stack unwinding.
struct Struct { int m; };
Struct& f(Struct&& rvalue)
{
std::cout << &rvalue << '\n';
return rvalue;
}
void main()
{
Struct& lvalue1 = f(Struct{ 1 });
std::cout << &lvalue1 << '\n';
Struct& lvalue2 = f(Struct{ 2 });
std::cout << &lvalue2 << '\n';
std::cin.get();
}
Output:
00A3F844
00A3F844
00A3F838
00A3F838
This code produces different addresses for the rvalues. Does that mean that actual constructing of Struct objects happens before a function call and I can safely do this kind of things?
I can safely do this kind of things?
No. Struct{ 1 } and Struct{ 2 } construct temporary objects which get destroyed after the full expression. That means the reference lvalue1 and lvalue2 are always dangled. Dereference on them leads to undefined behavior.
All temporary objects are destroyed as the last step in evaluating the full-expression that (lexically) contains the point where they were created

Address-of operator (&) vs reference operator(&)

I am kind confused about this case:
Declare a pointer:
int b =10;
int*a=&b;
Here & takes the address of b.
Consider another example:
/* Reference to the calling object can be returned */
Test& Test::func ()
{
// Some processing
return *this;
}
this should be a pointer, *this is the obeject pointed.
But here we are asking to assign *this to &Test.
What should we modify the code to let the function return the address. Should we still use Test& ?
In C++ there're two different syntax units:
&variable; // extracts address of variable
and
Type& ref = variable; // creates reference called ref to variable
Easy usage example:
int v = 5;
cout << v << endl; // prints 5
cout << &v << endl; // prints address of v
int* p;
p = &v; // stores address of v into p (p is a pointer to int)
int& r = v;
cout << r << endl; // prints 5
r = 6;
cout << r << endl; // prints 6
cout << v << endl; // prints 6 too because r is a reference to v
As for using references in functions, you should google "passing by reference in C++", there're many tutorials about it.
Firstly, this is a pointer. The * dereferences the pointer, meaning return *this; returns the object, not a pointer to it.
Secondly, Test& is returning a reference to a Test instance. In your case, it is a reference to the object. To make it return a pointer, it should be Test*.
It makes more sense if you read a pointer declaration from right to left.
Test* func(); //When I call func, and dereference the returned value, it will be a Test
But here we are asking to assign *this to &Test.
No... you're asking for the value/expression *this to be used to return a Test&, which is a reference to a Test object. What that does is return a reference to the object on which func() is invoked.
What should we modify the code to let the function return the address. Should we still use Test& ?
You should use Test* instead... pointers are addresses, and having changed the return type you could return this (which is a pointer), but not *this because *this is not a pointer.

why c++ rvalue is not immutable

Code:
void test(int&& a)
{
a++;
std::cout << a << std::endl;
}
and execute:
test(0);
why output 1? Cause I think 0 is rvalue, it could not be changed.
If you bind a non-class, non-array prvalue, such as a numeric literal, to a reference, the reference will actually be bound to a temporary variable which is a copy of the original value. That is,
int&& a = 0;
creates a temporary int object with the value zero, and then binds a to that.
When you call test(0), the same rule applies, and the reference parameter is bound to a temporary; the temporary is incremented and you get the result 1. Of course you are not incrementing 0 itself.
Both rvalue references and lvalue references to const can bind to a temporary. The difference is, the former is modifiable, the latter is not.
int& i = 0; // invalid
const int& i = 0; // valid
++i; // invalid
int&& i = 0; //valid
++i; // valid;