No Runtime Overhead
Since I have the privilege of using C++11/14 on my current project, I’ve been using the new language idioms as fast as I can discover and learn them. For example, instead of writing risky, exception-unsafe, naked “new“, code like this:
I’ve been writing code like this instead:
By using std::unique_ptr instead of a naked pointer, I don’t have to veer away from the local code I’m writing to write matching delete statements in destructors or in catch() exception clauses to prevent inadvertent memory leaks.
I could’ve used a std::shared_ptr (which can be copied instead of “moved“) in place of the std::unique_ptr, but std::shared_ptr is required to maintain a fatter internal state in the form of strong and weak owner counters. Unless I really need shared ownership of a dynamically allocated object, which I haven’t so far, I stick to the slimmer and more performant std::unique_ptr.
When I first wrote the std::unique_ptr code above, I was concerned that using the std::move() function to transfer encapsulated memory ownership into the safeTgtList vector would add some runtime overhead to the code (relative to the C++98/03 style of simply copying the naked pointer into the scaryTgtList vector). It is, after all, a function, so I thought it must insert some code into my own code.
However, after digging a little deeper into my concern, I discovered (via Stroustrup, Sutter, and Meyers) that std::move() adds zero runtime overhead to the code. Its use is equivalent to performing a static_cast on its argument – which is evaluated at compile time.
As Scott Meyers stated at GoingNative13, std::move() doesn’t really move anything. It simply prepares for a subsequent real move by casting its argument from an lvalue to an rvalue – which is required for movement of an object’s innards. In the previous code, the move is actually performed within the std::vector::emplace_back() function.
Quoting Scott Meyers: “think of std::move() as an rvalue_cast“. I’m not sure why the ISO C++ committee didn’t define a new rvalue_cast keyword instead of std::move() to drive home the point that no runtime overhead is imposed, but I’d speculate that the issue was debated. Perhaps they thought rvalue_cast was too technical a term for most users?
Update 10/25/13
As I said early in the post, the code example is “like” the code I’ve been writing. The real code that triggered this post is as shown here:
Since each “entry” CfarDetState object must be uniquely intialized form it’s associated CfarCrossing object, I can’t simply insert estd::make_unique<CfarDetState>() into the emplace_back() function. All of the members of each “entry” must be initialized first. Regardless of whether I use emplace_back() or push_back(), std::move(entry) must be used as the argument of the chosen function.
To your last question, I strongly suspect it’s because most programmers avoid casts as bad practice, I think for good reason. Move on the other hand is something to be encouraged. Using a cast blurs a fairly solid line.
The move constructor itself does have overhead though. And it will be called within emplace_back. Typically, std::move prepares for performance overhead.
To respond to your query of why we have `std::move()` instead of `rvalue_cast`, I think it’s probably because the former name implies intent, whereas the latter implies mechanism. It’s worth remembering that not all casts are free: `dynamic_cast` carries not insignificant runtime overhead.
> rvalue_cast keyword instead of std::move()
By the time it was realized that naming it std::move() isn’t ideal, it was deemed “too late” to change.
Not sure where/in which presentation I’ve heard it (Bjarne, Scott, GoingNative2013, ACCU?).
Thanks for the input guys.
Why not just do
safeTgtlist.emplace_back( ) ? You don’t need to create 2 temporaries move between them , and then move again to the vector.
You could do
safeTgtList.emplace_back( new MyPointer() ); as well if you have something to put in it.
Nevermind in the rare case the vector fails it would leak, put it in a temporary.
Using std::unique here is an improvement, but it may not be optimal: consider inserting RawSignals themselves into the std::vector, rather than smart pointers to RawSignals, if possible. This can simplify the code (pretty significantly!) and more clearly express ownership.
The code fragment is simply an example. What if RawSignal was large with lots of data members , some of which were other classes? Copying large objects is much more expensive than copying or moving pointers.
The code uses emplace_back, which constructs objects in-place instead of performing any copy or move constructors. If, in actual code, the signal was being constructed and then placed into the list, the code could instead use emplace_back to guarantee that you don’t have to copy a RawSignal.
Additionally, even if you don’t use emplace_back in such a circumstance, the compiler will generally perform copy elision and optimise your code to do exactly the same as what would happen if you *did* use emplace_back.
Please see the update.
Your compiler would probably end up eliding the copies and constructing in place if dets contained CfarCrossings rather than unique_ptr. When I first discovered copy elision I was freaked out by how good modern compilers are at it.
Once again, good point Abigail. When I’m writing app code, I get uncomfortable when I think too much about compiler implementations and making assumptions about what it should do under the covers. Thanks 🙂
If you make CfarDetState constructible from (CfarCrossing, int) could you call estd::make_unique in the emplace_back call? Any exception would be thrown before emplace_back was called.
Yes, I could and I will. Thanks Guy!