Intermediate C++ Game Programming Tutorial 20

From Chilipedia
Revision as of 05:41, 21 October 2019 by R vdw (Talk | contribs) (Video Timestamp Index)

Jump to: navigation, search

In this video we learn about r-value reference and move semantics, which is perhaps the most important feature that was added in the C++11 update. This is going to allow us to manage and transfer our resources in a precise and efficient manner. It is sexy as fuck and I love it.

Topics Covered

  • r-values and l-values
  • r-value reference function overloading
  • Move constructor and move assigment
  • Rule of 5
  • std::move
  • std::make_move_iterator

Video Timestamp Index

Tutorial 20


  • Why are move semantics important and required? 0:23
    • Example use case: adding objects to a container using std::vector::emplace_back
    • Instead of deep copying every element of the container when expanding the container capacity, move semantics allow you to direct pointers in the new container to existing blocks of memory
  • What is an rvalue (simple definition) 2:48
    • rvalues are values that can only appear on the right hand side of an assignment operation (=), such as:
    - literals; 69 = 420; is invalid
    - temporary return values of functions; f() = 420;, where the signature of f is int f();, is invalid
    • lvalues are values that can appear on the left hand side of assignments; they can be assigned to, for example:
    - x = 10; if x has been declared as int x;
    - obj.Get() = 69; if the member function Get() returns a reference to an integer member variable
    - arr[3] = 10; if arr has been declared as an array of integers
  • Why is the distinction between rvalues and lvalues useful? 4:57
    • The ability to detect whether a function is called with an rvalue (rather than with an lvalue) is useful, because the function can freely mangle an rvalue as it will be destoyed after the function call anyway
  • Overloading a function to take an rvalue reference using && 6:11
    • Using the && declarator in a function signature, e.g. void SomeFunc(SomeType&& donor)
    • This makes the compiler dispatch a function call to the overloaded version when an rvalue is passed
      (note: && was added in c++11; & is the familiar lvalue reference declarator)
  • std::move(): Converting a variable into an rvalue reference 8:00
    • This will allow the function to steal/use the resource(s) that are passed in
    • This can be useful if you don't need those resources anymore
    • std::move(someVar) is shorthand (from the STL) for the static cast static_cast<Type&&>(someVar)
    • All the STL containers are already wired up to be able to take advantage of "move semantics"
    - For example: std::string a = std::move(b) will leave b (also a std::string) empty after execution
    - There is no magic to this call; all it does is pass b as an rvalue reference, calling the overloaded constructor (the "move constructor"), and the overloaded assignment operator ("move assignment") of std::string in which the contents of b are passed over to a and removed from b.
  • Move constructor and Move assignment 10:07
    • The STL containers will try to use move semantics as much as possible
    • To benefit from this, any custom classes (or rather: any Type) will need to define a move constructor and a move assignment operator
    • std::move will default to the normal (lvalue reference) constructor and copy assignment operator if their move (rvalue reference) counterparts are not defined
  • Implementing the move constructor and move assignment operator in the Surface class of the Sprite drawing project 11:09
    • Signature of the move constructor: Surface(Surface&& donor) noexcept that pilfers the data from the donor
    • Signature of the move assignnment: const Surface& operator=(Surface&& rhs) noexcept
  • The full story on implicit compiler definition of special member functions and the "Rule-of-5" 14:22
    • If you implement any of the special member functions, you should implement all of them:
    - Destructor
    - Copy constructor
    - Copy assignment
    - Move constructor
    - Move assignment
  • The behavior of the default move members and the "Rule-of-0" 15:17
    • If a class only contain data members (Types) that handle their own RAII behavior (such as the STL containers), then you don't need to supply any move member functions
    • If a function is called on an STL container, you don't need to specify move semantics, the container implementation takes care of it.
    - No need for a function overload of void SomeFunc(std::string&& donor).
    if the default function is defined as void SomeFunc(const std::string& str),
    just use a single void SomeFunc(std::string str) that takes (in this example) a string by value
    - If an lvalue is passed as the argument, the string will be copied into the local variable
    - If an rvalue is passed as the argument, the string will be moved into the local variable
  • Fix the Surface class to include the case (or: the state) where a surface is empty 19:09
    • This has become necessary because the a move operation will leave the donor object empty
  • Using a std::make_move_iterator to apply the std::copy_if algorithm to act as a "move-if" operator on containers 21:00
    • The make_move_iterator is a convenience function that constructs a std::move_iterator<Iter>(i) which can be used to move out from the elements accessed through the input iterator i
    • Example code written to move strings from a vector of strings when they are larger than a certain threshold
  • Recap: why move semantics are neat and lead into smart pointers
    • Move semantics: one of the most important additions that came with C++11. The functionality gives us finer control over how our resources are managed, and we can re-use resources of objects that would otherwise be discarded/deleted
    • Elegant way of being able to move resources from one container into another
    • Lets us create Types that cannot be copied, but that can still be moved: unique non-copyable types (no duplicates can exist), but that you can still have in a container, have that container grow, or move objects around from one container to the other
    • Move semantics enable the concept of smart pointers (unique_ptr, the most important smart pointer, is a type that has the above characteristics (next up in Tutorial 21)

Note

If you are using Visual Studio 2017, you might notice that our move members are not being used during std::vector growth even after we have 'properly' implemented them. This is expected, so don't pay it much mind at the moment.

The reason for this is that std::vector will only use the move members if they are guaranteed not to throw any exceptions (as per the standard). This issue will be dealt with in Intermediate 22 (tutorial on exceptions). For your interest, you can enable the move optimization by declaring your move ctor as Surface( Surface&& ) noexcept. The same should be done for the move assignment and the destructor.

You might wonder why Chili had no problems in the video. In the video, Chili was using Visual Studio 2015, which does not conform to the standard in this point, so it uses the move members regardless of whether or not they are marked noexcept.

Source Code

See also