I would like to know if the following code is valid.
In particular, I'm confused about the lifetime of the objects involved here after a call to new_S.
From my understanding, T will be copied when processing the initializer list and possibly in the vector move constructor.
What about the RValue vector ? is it still valid after returning from new_S ? I would say no but I'm definitely not sure
struct T
{
int t;
};
struct S
{
S(std::vector<T>&& s) : s_(std::forward<std::vector<T>>(s)) {}
std::vector<T> s_;
};
S* new_S()
{
return new S{{{1}, {2}, {3}}};
}
After returning from new_S
there is no "RValue vector" (I assume you mean the rvalue reference parameter to the constructor). The rvalue reference exists only during the construction of S.
A few words to your code: std::forward
is worng here. It's needed only for perfect forwarding of unviersal references, and std::vector<T>
uns not an universal ref but a rvalue ref (see here). Use std::move
in this case.
Having said that, you should not pass by rvalue-ref in you constructor. Instead, pass by value:
struct S
{
S(std::vector<T> s) : s_{std::move(s)} {}
std::vector<T> s_;
};
That way you will have the best solution for any value passed, see here:
s
is move-constructed from the argument, after that, s_
is move-constructed from s
. No copies are made.s
is copy-constructed, after that, s_
is move-constructed from s
. Only the one necessary copy is made.Last but not least: don't use raw pointers and new
/delete
. Instead, use smart pointers:
unique_ptr<S> new_S()
{
return std::make_unique<S>({{1}, {2}, {3}});
}
make_unique
comes with C++14, you can easily roll your own if you library does not have it yet.
Update:
To answer your question about object lifetimes: In new_S
, you have basically 4 or 5 objects:
s
is an object of its own.Now what happens:
s
is initialized with the temporary, calling vector's move constructor. After that, the temporary is empty and s
owns the chunk of memory with the T's.s_
, the parameter is moved. That means, s_
gets move constructed from the temporary (in your writing) or from s
(in my writing). After that, the temporary/s
are empty and s_
owns the chunk of memory allocated in step 1.s_
is properly constructed, and it's a valid object. An object X
becomes only "invalid" when move it, i.e. when you call move(X)
. An object Y
you construct by moving another object X
to it (meaning: move-constructing it via auto Y = move(X);
) never is invalid. Invalidating objects on construction would be extremely bad and useless, C++ would be a broken language.