Thread: Rvalue references and move semantics in C++11

  1. #1
    Administrator webmaster's Avatar
    Join Date
    Aug 2001
    Posts
    1,012

    Rvalue references and move semantics in C++11

    I just posted the latest article in the series on C++11, covering rvalue references and move semantics--using move constructors and move assignment operators to solve the problems C++ has had with unnecessary copies of temporaries: Rvalue References and Move Semantics in C++11 - Cprogramming.com

    Comments and suggestions welcome as always!

  2. #2
    [](){}(); manasij7479's Avatar
    Join Date
    Feb 2011
    Location
    *nullptr
    Posts
    2,657
    Great!
    That was my... least confusing ... rvalue reference read till date!
    I think I 'get' the point quite well now.

  3. #3
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Nice article.

    I will point out, as someone who has done a fair amount of high-performance programming in C++-03 and earlier, that it has never been particularly difficult to avoid creating temporaries with careful design that avoids creating the temporaries in the first place.

    My recurring beef with move constructors is that, originally, they were introduced to cope with sloppy design, and there is more involved conceptually with a move constructor than just doing a destructive copy. Move constructors are about removing unwanted copies after writing code that (implicitly) creates them. From a design perspective, that is poor: it is changing the semantics of code and, if you don't want the overhead of temporary objects, then you should avoid creating them in the first place.

    As an example, in C++-03, the way to achieve the equivalent of your doubleValues() is
    Code:
    std::vector<int> &DoubleValues(std::vector<int> &v)
    {
         for (std::vector<int>::iterator i = v.begin(), end = v.end(); i != end; ++i)
         {
              *i *= 2;
         } 
         return v;
    }
    This function does not create a temporary. The caller might, depending on how it uses the function, but can avoid doing so.

    For example;
    Code:
    int main()
    {
        std::vector<int> v;
        for ( int i = 0; i < 100; i++ )
        {
            v.push_back( i );
        }
    
        doubleValues(v);    // no temporary introduced
    
        v = doubleValues(v);    //  temporary introduced, can be eliminated by use of move constructor
    
        std::vector<int> y = doubleValues(v);     //  no temporary here (copy constructor invoked).   Explicit copy of v created.
    
        std::vector<int> &z = doubleValues(v);    //   no temporary, but two names (z and v) now reference the same vector.
    }
    By designing doubleValues() as above, the need for the caller to do "v = doubleValues(v)" is eliminated, which is the only use case above that benefits from introduction of move constructors. The only thing that is not eliminated is the tendency for sloppy programmers to do "v = doubleValues(v)" anyway in code that is compiled as C++-03. So, in effect, the purpose of the move constructor is just facilitating lazy programming.

    I have seen plenty of examples like yours, but I have also yet to find a real-world case where C++-03 code designed for performance gets either a measurable performance change when recompiled as C++-11, or any real opportunity to actually simplify the code.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  4. #4
    Administrator webmaster's Avatar
    Join Date
    Aug 2001
    Posts
    1,012
    Thanks for the comments! I agree that the example I gave can be rewritten by taking a mutable (lvalue) reference parameter (that's how I would write it pre-move semantics). However, I can imagine cases where this isn't ideal--for example, if the function were going to compute the size of the vector to return, it would be better to do it once, rather than construct a vector and then resize it. You could solve that by allocating memory in the function and returning a pointer, but again, you're doing more work than you need. (Perhaps this is what you mean by not finding a measureable difference?)

    I tend to disagree that this is inherently *sloppy* code though; it's natural code, that happens to be sloppy because of how C++ works, not because assigning a variable to a value returned from a function is evil Arguably, move semantics make the language *easier* for beginners, in the sense that you don't have to learn some weird way of returning a value for the sake of efficiency. (You eventually need to learn a move constructor and move assignment operator to use it on your own classes, but you'd probably get to that once the student is much more experienced.)

    By the way, another situation where I can see rvalue references being used on a practical basis is with code that returns reference counted smart pointers. By avoiding the copy, you can skip an add/release refcount cycle and the cache flushing due to atomic operations (or locking, if not using atomics) that would accompany it.

  5. #5
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Quote Originally Posted by webmaster View Post
    For example, if the function were going to compute the size of the vector to return, it would be better to do it once, rather than construct a vector and then resize it. You could solve that by allocating memory in the function and returning a pointer, but again, you're doing more work than you need. (Perhaps this is what you mean by not finding a measureable difference?)
    More or less. In this example, code crafted for performance to compute required size of a vector, would compute the required size without creating a vector (or dynamically allocating memory) at all, let alone resizing it. Granted it can take more intellectual effort by the programmer to implement that computation, but the dividends - if one is performance or resource conscious - are significant.

    Quote Originally Posted by webmaster View Post
    I tend to disagree that this is inherently *sloppy* code though; it's natural code, that happens to be sloppy because of how C++ works, not because assigning a variable to a value returned from a function is evil
    I am not arguing that assigning a variable to a value returned from a function is evil. However, it is no more natural to do that in C++ for large data structures (or for variables with operations that implicitly do lots of memory allocation and deallocation). Yes, the syntax makes it easy to do that, but it is no more "natural" to do that in C++ than it is any other language.

    The justification for move semantics has, almost invariably, been performance-critical code. Key considerations when designing performance-critical code - in any programming languages - have always been minimising the need to wait for resources, minimising cases where "large" data structures are copied around, minimising or eliminating opportunities for "temporary" objects to be created.

    There are many things covered in my comment above "minimising the need to wait for resources". It means discouraging (ideally eliminating) memory allocation and deallocation. It almost invariably eliminates synchronisation (for example between threads) and doing it in a part of the code that is less performance sensitive. It means keeping the performance critical code as small and simple as possible (so, for example, it can persist in a processor cache). It means minimising unnecessary I/O (while sometimes the I/O itself is the performance critical bit - for example, updating a sprite on a screen - unnecessary I/O compromises performance by several measures).

    Quote Originally Posted by webmaster View Post
    Arguably, move semantics make the language *easier* for beginners, in the sense that you don't have to learn some weird way of returning a value for the sake of efficiency. (You eventually need to learn a move constructor and move assignment operator to use it on your own classes, but you'd probably get to that once the student is much more experienced.)
    I'm all in favour of making a language easier for beginners. However, excessive handling of temporaries has rarely been the language feature (in C++) that compromises performance of code written by beginners. The most common impediment to performance of code written by beginners - in any language - is their recurring penchant for premature optimisation: they try to do things in complex ways that they believe are more efficient, but really are not.

    Excessive handling of temporaries does compromise performance of hand-crafted code translated from other languages into C++. But that is because code optimised for one language is rarely optimal when translated simplistically and mechanistically into another programming language. Granted, move semantics do help in those cases. The catch is that they give a false sense of confidence - handling of temporaries is only one of several things that need to be accounted for when migrating between programming languages. Encouraging use of techniques from other languages, that don't map well into C++, is not a good idea unless the entire programming methodology carries across. Move semantics are a small part of that, not a complete solution.

    There is a fine balance between making life easier for beginners and giving them a false sense of confidence. Move semantics as a justification for beginners worrying less about performance is pretty close to that edge.

    Granted, people who actually need to write performance-critical code, do find themselves having to delve into how their programming language of choice manages "temporaries". However, those folks also generally need to be able to justify through analysis (any testing is in support of the analysis, not the reverse) that their code meets performance requirements. Move semantics can make that analysis more challenging - because it can change the semantics of the code.

    Quote Originally Posted by webmaster View Post
    By the way, another situation where I can see rvalue references being used on a practical basis is with code that returns reference counted smart pointers. By avoiding the copy, you can skip an add/release refcount cycle and the cache flushing due to atomic operations (or locking, if not using atomics) that would accompany it.
    That presumes that performance critical code actually uses functions that return reference counted smart pointers.

    In practice, as I mentioned above, performance critical code generally avoids all sorts of operations that involve waiting for resources. If performance matters in some segment of code, then a first step would be to eliminate any reference counting, locking, or use of atomics, not rely on language features or compiler smarts to better facilitate their use.
    Last edited by grumpy; 11-13-2011 at 11:09 PM.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  6. #6
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    Yes, the syntax makes it easy to do that, but it is no more "natural" to do that in C++ than it is any other language.
    It very much is natural to return objects by value in C++ precisely because C++ can do it properly, that includes expensive objects.

    Manually creating an object and passing a reference to a function is very natural to C and many others.

    Native move semantics don't change anything; you can setup a series of classes to get the same source and performance characteristics that "rvalue references" provide. Giving C++ a native mechanism makes it clear, consistent, and easier to implement, but that doesn't change what constitutes a good design or a compromised design.

    Good design principles don't change because an object is expensive. Good coding practices don't change because objects are expensive. The truth is, programmers shouldn't care what hoops the expression "vector v2 = doubleValues(v1);" has to jump through to be correct, consistent, and offer quality performance.

    What you are talking about is trading the clearest, as C++ gets, intent at the most relevant location for performance because the benefit represents very real issues. You are ready to accept this because you are an "old hat" at this particular game. I don't blame you for that, and I certainly understand it. However, you shouldn't be so willing to accept it. Programming for the real world is difficult enough in any language; there is no reason to celebrate old methods when they make the job more consuming.

    Soma

  7. #7
    Registered User hk_mp5kpdw's Avatar
    Join Date
    Jan 2002
    Location
    Northern Virginia/Washington DC Metropolitan Area
    Posts
    3,817
    You've got this in a few places:
    Code:
    ~ArrayWrapper ()
    {
        delete _p_vals;
    }
    Shouldn't that be:
    Code:
    ~ArrayWrapper ()
    {
        delete [] _p_vals;
    }



    Just a very minor nit but this:
    Code:
    MetaData (int size, std::string name)
        : _name( name )
        , _size( size )
    {}
    Should be:
    Code:
    MetaData (int size, const std::string& name)
        : _name( name )
        , _size( size )
    {}
    "Owners of dogs will have noticed that, if you provide them with food and water and shelter and affection, they will think you are god. Whereas owners of cats are compelled to realize that, if you provide them with food and water and shelter and affection, they draw the conclusion that they are gods."
    -Christopher Hitchens

  8. #8
    Administrator webmaster's Avatar
    Join Date
    Aug 2001
    Posts
    1,012
    @grumpy phantomotap captured my feelings exactly on what is vs isn't natural. I think once we've written enough C++ and "know" what is going on behind the scenes that writing unnatural code can start to feel natural

    @hk_mp5kpdw Thanks, good catches!

  9. #9
    Registered User
    Join Date
    May 2010
    Posts
    4,633
    Another small nit. Variables beginning with an underscore are reserved for the implementation.
    1
    Certain sets of names and function signatures are always reserved to the implementation:
    — Each name that contains a double underscore _ _ or begins with an underscore followed by an uppercase
    letter (2.12) is reserved to the implementation for any use.
    — Each name that begins with an underscore is reserved to the implementation for use as a name in the
    global namespace.
    Jim

  10. #10
    Administrator webmaster's Avatar
    Join Date
    Aug 2001
    Posts
    1,012
    @jimblumberg This is something I investigated a while ago as the current coding style in my organization matches the style I used in the article. I believe that the quoted section does not apply to my use of leading underscores since they aren't part of the global namespace and are never followed by a capital letter.

  11. #11
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Quote Originally Posted by phantomotap View Post
    I don't blame you for that, and I certainly understand it. However, you shouldn't be so willing to accept it. Programming for the real world is difficult enough in any language; there is no reason to celebrate old methods when they make the job more consuming.
    You misunderstand my focus completely.

    I am not celebrating old methods and pooh-poohing new ones. I realise that a lot of programmers are happy to accept things being done implicitly, in order to reduce their requirements or design burden. I have in mind a real-world regulatory framework that requires explicit analysis of possible code manipulations that a compiler might apply to an expression.

    I think I'll let the discussion drop there though. I deal with enough politics at work with developers trying to short-cut assurance needs, and don't feel like arguing it at home.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  12. #12
    Registered User
    Join Date
    Mar 2011
    Posts
    546

    example code

    It helped me to understand the article by coding up the vector copy/move (similar to the example) in MSVC 2010 and MSVC 2008 (with fixes to remove the C++ 11 features like 'auto'). In both versions I printed the address of the first element in the destination vector in both the function and in the resulting value that the caller ends up with. In VC2008 without move semantics, the address of the first element is different in both the function and the result. so an invisible copy was performed as expected. In the Vc2010 version, the address is the same in both places as expected, so a move happened.

    Code:
    #include <cstdio>
    #include <vector>
    
    std::vector<int> copy(std::vector<int> &a) 
    {
    	std::vector<int> b;
    
    	for(auto p = a.begin();p != a.end();++p) {
    		b.push_back(*p);
    	}
    
    	printf("a = %p b = %p b[0] = %p\n",&a,&b,&b[0]);
    
    	return b;
    }
    
    int main(int argc,char *argv[])
    {
    	std::vector<int> x;
    	x.push_back(1);
    
    	std::vector<int> &&y = copy(x);
    
    	printf("y = %p y[0] = %p\n",&y,&y[0]);
    
    	std::vector<int> z = copy(x);
    
    	printf("z = %p z[0] = %p\n",&z,&z[0]);
    
    	return 0;
    }
    but that led me to another question. notice that I could declare the destination vector as either a plain old 'vector<int> z' or 'vector<int> &&y' and the results are the same. I am not clear on what the difference is between the two declarations. My guess: in both cases I have an instance of the vector on the local stack frame, but in the case of the &&y I have an extra level of indirection because its a reference to the vector ?

  13. #13
    Administrator webmaster's Avatar
    Join Date
    Aug 2001
    Posts
    1,012
    The way to think about it is that in the case of &&y, you have a reference to a temporary variable, just as if you'd written:

    Code:
    const std::vector<int> &y = copy(x);
    C++ compilers generally handle returning objects larger than will fit in a register by passing a pointer to stack allocated memory as a hidden parameter to the function and storing the returned, temporary object at the provided address. This is why you see an address that makes it look like a local variable declared on the stack.

  14. #14
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Quote Originally Posted by webmaster View Post
    The way to think about it is that in the case of &&y, you have a reference to a temporary variable, just as if you'd written:

    Code:
    const std::vector<int> &y = copy(x);
    It is debatable whether "temporary" is the right word as, in both cases it is bound to y, so continues to exist for as long as y is in scope.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  15. #15
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by grumpy
    It is debatable whether "temporary" is the right word as, in both cases it is bound to y, so continues to exist for as long as y is in scope.
    If I remember correctly, "temporary" is the word used in the standard.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Returning a rvalue from function
    By Memloop in forum C++ Programming
    Replies: 11
    Last Post: 09-22-2009, 02:47 PM
  2. lvalue vs rvalue casting
    By fallout01 in forum C Programming
    Replies: 5
    Last Post: 07-12-2008, 02:35 AM
  3. lvalue rvalue discussion
    By George2 in forum C++ Programming
    Replies: 18
    Last Post: 03-04-2008, 05:48 AM
  4. Get lvalue through rvalue
    By George2 in forum C++ Programming
    Replies: 4
    Last Post: 12-17-2007, 08:53 AM
  5. declare references to references works!
    By ManuelH in forum C++ Programming
    Replies: 4
    Last Post: 01-20-2003, 08:14 AM