Question

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}}};
}
Was it helpful?

Solution

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:

  • If an rvalue is passed, s is move-constructed from the argument, after that, s_ is move-constructed from s. No copies are made.
  • If an lvalue is passed, 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:

  • The S that gets constructed and the vector inside it
  • The initializer list you pass to the constructor
  • a temporary vector constructed from the initializer list
  • in my writing of the function, the parameter s is an object of its own.

Now what happens:

  1. The initializer list is used to create the temporary vector object, wich is the rvalue that gets passed to the constructor. During construction of the temporary, one chunk of memory is allocated to place the three T's inside the vector.
  2. In your writing, the rvalue ref parameter of the constructor is bound to the temporary. In my writing, 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.
  3. In the initialization of 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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top