Home > C++11 > Looks Weird, But Don’t Be Afraid To Do It

Looks Weird, But Don’t Be Afraid To Do It

Modern C++ (C++11 and onward) eliminates many temporaries outright by supporting move semantics, which allows transferring the innards of one object directly to another object without actually performing a deep copy at all. Even better, move semantics are turned on automatically in common cases like pass-by-value and return-by-value, without the code having to do anything special at all. This gives you the convenience and code clarity of using value types, with the performance of reference types. – C++ FAQ

Before C++11, you’d have to be tripping on acid to allow a C++03 function definition like this to survive a code review:


std::vector<int> moveBigThingsOut() {

  const int BIGNUM(1000000);

  std::vector<int> vints;

  for(int i=0; i<BIGNUM; ++i) {
    vints.push_back(i);
  }

  return vints;
}

Because of: 1) the bolded words in the FAQ at the top of the page, 2) the fact that all C++11 STL containers are move-enabled (each has a move ctor and a move assignment member function implementation), 3) the fact that the compiler knows it is required to destruct the local  vints object just before the return statement is executed, don’t be afraid to write code like that in C++11. Actually, please do so. It will stun reviewers who don’t yet know C++11 and may trigger some fun drama while you try to explain how it works. However, unless you move-enable them, don’t you dare write code like that for your own handle classes. Stick with the old C++03 pass by reference idiom:


void passBigThingsOut(std::vector<int>& vints) {

  const int BIGNUM(1000000);

  vints.clear();

  for(int i=0; i<BIGNUM; ++i) {
    vints.push_back(i);
  }

}

Which C++11 user code below do you think is cleaner?


//This one-liner?

auto vints = moveBigThingsOut();

//Or this two liner?

std::vector<int> vints{};
passBigThingsOut(vints);

  1. April 4, 2014 at 12:23 am

    Nice feature, but the code itself will lead to confusion. Why? It is an inconsistent behavior, depending on the data type being an STL or not. In a small program, this is fine, because you can easily check it.

    On a big project, there is a myriad of alias or worse, a macro. So, in order to find out if the function returns properly, you have to trace the variable’s origin. In addition, when you add a legacy code, it adds to the confusion — is it build with C++11 or just a bad code? Personally, after reading lots of old code, I rather see an explicit convention to distinguish the “move()” behavior than to have this implicitly efficiency.

  2. April 4, 2014 at 5:13 am

    Thanks for the feedback. In the interim, before the new C++11 idioms and style take hold, I agree with you. Five or so years from now, I think returning move-enabled containers by value will be mainstream. It will be interesting to see what Scott Meyrs says about it in his upcoming book.

  3. April 4, 2014 at 4:21 pm

    Actually, many compilers will generate efficient code for this, even using C++03. The feature is called RVO, for “return value optimization”. If the compiler notices that the return always the same (i,e the function doesn’t have “return foo” in one place and “return bar” in another), then it can generate code to construct “vints” in the memory location for the outer “vints”.

    This is even faster than “moving it out” (nothing is copied, nothing is moved), and works on objects that are not move-enabled.

  4. Aaron
    April 4, 2014 at 4:22 pm

    I certainly like the one-liner although I’m very leery about all the advice to use auto anywhere and everywhere you can. It seems to me like using auto obfuscates the code because I need to now go look at the definition of moveBigThingsOut as I’m reading the code to know what the type of the variable is.

    I think that this would be even cleaner and easier to read:

    std::vector< int > vints = moveBigThingsOut();

    I’ve been experimenting with C++11 and auto and I find that it is useful for very verbose types such as container iterators or in template code where you don’t know what the type is. Other than that and it just feels like being lazy to me.

  5. April 4, 2014 at 6:45 pm

    If the function definition is available in the same compilation unit, then the compiler won’t even generate a move. The vector in moveBigThingsOut() will be constructed at the site where it is called.

    This is called Named Return Value Optimization, and it is also available in C++03. (That is, eliding the copy construction is not forbidden in C++03.)

  6. Jens
    April 5, 2014 at 4:16 am

    Even in the old C++03 days, the first functions would have been better. Compilers optimize the code and remove the copying of the return value since ages. This is called return vlue optimization (or named return value optimization). The standard contains an explicit exception for this to allow the compiler to change the observable behavior by eliding the call of the copy constructor. Instead, the return object is directly constructed in the callers stack frame. Some compilers, e.g. msvc, even do this when optimizations are disabled.

    So, I would argue that the first versions always was the preferred one because it favors readability and clarity over performance. It should be changed only when profiling shows that the copy-constructor is not removed and it actually matters.

  7. jaxanm
    April 7, 2014 at 3:13 am

    There is however one optimization not possible with the former style. Namely the reuse of buffers. Consider the situation where you call moveBigThingsOut a lot, then each call will result in an allocation. But when calling passThingsOut a lot, you can choose to reuse the same vecter each time, and get away with just one allocation. This was discussed in some talk about a getline API.

  8. April 7, 2014 at 2:42 pm

    Geeze, every time I write a C++ post, I get so many counter-intelligence comments that I wished I never wrote the post in the first place. 🙂

  9. Rob G
    April 8, 2014 at 6:32 am

    Actually, move semantics should be preferred. The problem with RVO is that it is unpredicatable, and program behaviour will change depending on whether and how it is implemented. The wikipedia article illustrates this very well: http://en.wikipedia.org/wiki/Return_value_optimization

    In short, if you want to be sure, use move semantics. RVO is inconsistent.

  1. No trackbacks yet.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.