0

Do I understand right, if I call std::move() with a temporary object, like std::move(A()) or std::move(int), it returns a reference to free memory?

As I know, if T&& or const T& assigns to rvalue or prvalue, it creates an object in memory and continues the life-time of the object while the reference is not destroyed? Does a second assign to a reference continue the life-time of the object?

Also, if I call, for example: int x; static_cast<int&&>(x), as I know it is an xvalue statement (reference), but what happens with the life-time of the object?

int&& foo() {
    int n = 5;
    return std::move(n);
}

foo(); // can second rvalue reference continue life of object while exist ref to object?
int x = 0;

static_cast<int&&>(x); // what happens with life time of object? or
static_cast<const int&>(x);

std::move(0); // will it return free memory? or
std::move(std::string("hello, world!"));

I'm just a beginner, and I would appreciate it if you could correct me if I make any mistakes.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Kappy
  • 27
  • 3
  • Related: https://stackoverflow.com/q/69060639/5754656 https://stackoverflow.com/q/75372382/5754656 – Artyer Jun 21 '23 at 17:14
  • `static_cast` has no effect whatsoever on its argument. `std::move` is basically a cast also, it has an effect on its arg only if it is used. See Artyer comment that would correspond to the intent of your question, I think. – Oersted Jun 21 '23 at 17:19
  • std::move on an int will do nothing. std::move(0) will also do nothing. the last one will have no observable side effects (can be optmized away). You can see move as moving ownership to the lhs variable, so `std::string s1{"123"}; std::string s2{ std::move(s1); }` will tranfer the data from s1 to s2 and leave s1 in a state where you cannot use it anymore (it will no longer own the memory for the "123"); (SSO left out) – Pepijn Kramer Jun 21 '23 at 17:20
  • To be more precise on `std::move(x)` returns a rvalue reference to x, that is typically used as argument to a move constructor. This is this constructor that will possibly alter x value. – Oersted Jun 21 '23 at 17:23

2 Answers2

3

The lifetime of an object is determined when the object is created and never changes after that. There is nothing you can do to an already-existing object that will cause its lifetime to change.

For your specific examples:

int&& foo() {
    int n = 5;
    return std::move(n);
}

foo returns a dangling reference. The lifetime of n ends when foo returns. Since foo returns a reference to n, and n's lifetime has ended, any read or write to the reference returned by foo will result in undefined behavior.

int x = 0;

static_cast<int&&>(x);
static_cast<const int&>(x);

These casts do nothing. Even if the result was assigned to another variable, they would have no effect on the lifetime of x.

std::move(0);
std::move(std::string("hello, world!"));

These both return dangling references. 0 and std::string("hello, world!") both create temporary objects. Reference lifetime extension would extend their lifetimes to the lifetimes of std::move's argument (since they are bound to that reference immediately at the moment they're created), but that argument's lifetime is shorter than that of the temporary objects anyway.


Some of your confusion seems to come from the so-called "reference lifetime extension" feature of C++. This does not, however, extend the lifetime of existing objects. It can only extend the lifetime of an object as it is being created.

If a temporary object is bound to a reference immediately upon creation then that temporary object's lifetime is extended to the lifetime of the reference.

That is, reference lifetime extension applies in all of the following cases:

// Here the string literal "Hello World" is converted to a to a temporary std::string
// which has its lifetime extended to the lifetime of foo
const std::string& foo = "Hello World";

// Here the return value of func is a temporary int object
// which has its lifetime extended to that of i
int func() { return 42; }
const int& i = func();

Reference lifetime extension does not apply in these situations:

// The temporary object created by the int literal 42 does not have
// its lifetime extended to that of i because it is not bound to i
// at the moment of its creation.
const int& func() { return 42; }
const int& i = func();

// The lifetime of the temporary std::string object created from the
// string literal "some string" does not get extended to the lifetime
// of f.member_ref because it is not bound to member_ref at the moment
// of its creation
struct Foo
{
    const std::string& member_ref;
    Foo(const std::string& s) : member_ref(s) {}
};
Foo f("some string");
Miles Budnek
  • 28,216
  • 2
  • 35
  • 52
1

I think the confusion is because std::move() doesn't move anything. It is only a typecast. It doesn't participate in memory management at all.

If you have a function that takes advantage of the fact that its argument is an rvalue reference to do what would otherwise be unsafe (like invalidating the argument), the compiler will protect you from calling such a function with an lvalue. If, however, you want to call such a function with an lvalue, knowing that your variable may be invalidated, std::move() casts it to an rvalue so you can call the function.

It's the function, however, not the std::move, that potentially the invalidates the argument. The std::move just makes it possible to call such a function.

user1806566
  • 1,078
  • 6
  • 18