Saturday, May 4, 2013

rvalue Reference Moves (Now in Color!)


/**
 * The following is placed into a header file called character.h
 */
#ifndef CHARACTER_H
#define CHARACTER_H

#include <string>

class Grill
{
public:
Grill() : type(), model() { }

std::string type;
std::string model;
};


class Character
{
public:
/**
* Default constructor, this isn't very interesting. What
* is interesting is that C++11 does allow you to delete
* constructors instead of having them generated. If you
* didn't want a default constructor available, comment
* in the NO_DEFAULT_CTOR macro.
*/
#if defined NO_DEFAULT_CTOR
Character() = delete;
#else
Character();
#endif


/**
* Copy constructor that implements copy semantics.
* C++ developers are familiar with this form.
* If this constructor didn't exist it would be generated
* by the compiler.
*/
Character(Character const& c);


/**
* Move constructor that implements move semantics (you
* guessed it). The compiler will also try to generate
* this if it does not exist. The caveats are still the
* same, if a member of this class does not have move
* semantics then it requires this constructor OR requires
* that move semantics are defined for the member.
*/
Character(Character && c);


/**
* Assignment operator.
*/
Character operator=(Character const& rhs);


//! Just some dummy values so that we have something
//! in this class.
int age;
std::string name;
Grill grill[2];
};


/**
 * Sample function to return by value.
 */
Character ReturnByValue();


/**
 * Sample function that takes a character object and returns
 * a character by value.
 */
Character EchoByValue(Character c);

#endif

/**
 * End of character.h
 */
/**
 * The following is in a source file called character.cpp, not to
 * be compiled with main.cpp
 */
#include "character.h"
#include <iostream>

/**
 * I defined the character default constructor for this example if
 * the NO_DEFAULT_CTOR is not defined. I know this is a double
 * negative.
 */
#if !defined NO_DEFAULT_CTOR
Character::Character()
: age(0)
, name()
{
std::cout << "Character: Default Constructor" << std::endl;
}
#endif


/**
 * The following shows the "copy" semantics for the Character class.
 * This is old-style C++.
 */
Character::Character(Character const& c)
: age(c.age)
, name(c.name)
{
std::cout << "Character: Copy Constructor" << std::endl;
}


Character Character::operator=(Character const& rhs)
{
std::cout << "Character: Assignment Operator" << std::endl;
this->age = rhs.age;
this->name = rhs.name;
return *this;
}


Character::Character(Character&& c)
{
std::cout << "Character: Move Constructor" << std::endl;

// increment the age because we want to see some change.
this->age = std::move(c.age);
this->name = std::move(c.name);
}


Character ReturnByValue()
{
Character c;
std::cout << "ReturnByValue" << std::endl;
return c;
}


/**
 * The first thing this function does is invoke a copy constructor on
 * c. The new c object is local to this function.
 */
Character EchoByValue(Character c)
{
std::cout << "EchoByValue" << std::endl;
std::cout << " &temporary = " << reinterpret_cast<int>(&c) << std::endl;
return c;
}
/**
 * This entry is all about move semantics and rvalue references. Keep in
 * mind I am also learning this at the same time. Please refer to notes
 * about rvaue references in the last blog post for more information.
 */

// This example also highlights deleting constructors. If the macro
// below was commented in, we delete the default constructor and our
// example will fail to compile with error:
//
// "use of deleted function 'Character::Character()'"
//
// This is a neat feature to get rid of copy/move constructors!
//#define NO_DEFAULT_CTOR

// Include a character froma separate place. This is guaranteed to
// eliminate the problem with inlining that I saw yesterday where no
// copy and assignment was taking place.
#include "character.h"

#include <iostream>


int main(int argc, char** argv)
{
std::cout << "1) DEFAULT CONSTRUCTOR" << std::endl;
Character c1;

std::cout << "\n2) COPY CONSTRUCTOR" << std::endl;
Character c2(c1);

// The following only invokes the copy constructor, no assignment
// operator fired! This is a nice optimization in the compiler.
std::cout << "\n3) ASSIGNMENT BY VALUE" << std::endl;
Character c3 = c2;

// The following illustrates that the assignment operator works.
std::cout << "\n4) ASSIGNMENT" << std::endl;
Character c4;
c4 = c3;

std::cout << "\n5) ASSIGNMENT BY VALUE" << std::endl;
Character c5 = ReturnByValue();

// The following was a curious case where I wrote it thinking,
// "What will the compiler do?"- Think about it, we have an object
// c5 where it is currently on the stack and active. We try to
// create an rvalue reference to it. The rvalue reference should
// try to execute "move semantics" and use the move constructor.
// If it were to succeed, it should have moved the memory allocated
// by Character and place it into c6. What would happen to the values
// in c5? Would we be able to still access them? The compiler answers
// with a definitive, "Oh no you didn't!" The error message from the
// compiler reads:
//
// error: Cannot bind 'Character' lvalue to 'Character&&'
//
// Wow. That was a great smackdown from gcc. The keyword in the above
// error is "lvalue". We can't take lvalue's and assign them to
// rvalue references. It doesn't make sense!
#if defined ERROR_CANNOT_BIND

std::cout << "\n6/7) ASSIGNMENT BY RVALUE" << std::endl;
Character c6;
c6.age = 33;
c6.name = "JR";

Character&& c7 = c6;
std::cout << " c6.name = " << c6.name << std::endl;
std::cout << " c6.age = " << c6.age << std::endl;

std::cout << " c7.name = " << c7.name << std::endl;
std::cout << " c7.age = " << c7.age << std::endl;
#endif

// The next example is using an rvalue reference from a temporary
// in a function that was returned by value. What will happen?
std::cout << "\n8) ASSIGNMENT BY RVALUE FROM ReturnByValue()" << std::endl;
Character&& c8 = ReturnByValue();

// Well, that worked! I get one printout that a default constructor
// fired and that was it.

#if defined ERROR_UNINITIALIZED_REFERENCE
// The following is an obvious error, but I thought I'd throw it in
// anyways.
std::cout << "\n9) ASSIGNMENT BY RVALUE FROM ReturnByValue()" << std::endl;
Character&& c9;
c9 = ReturnByValue();
#endif

// Up until this point I haven't seen the move constructor getting fired.
// This time is different though!
std::cout << "\n10) PASS AND RETURN RVALUE" << std::endl;
c5.name = "JR";
c5.age = 32;

std::cout << " c5.age = " << c5.age << std::endl;
std::cout << " c5.name = " << c5.name << std::endl;
Character&& c10 = EchoByValue(c5);
std::cout << " c5.age = " << c5.age << std::endl;
std::cout << " c5.name = " << c5.name << std::endl;
std::cout << " &c10 = " << reinterpret_cast<int>(&c10) << std::endl;
std::cout << " c10.age = " << c10.age << std::endl;
std::cout << " c10.name = " << c10.name << std::endl;

// NOTE: Because we made a copy of the data inside of EchoByValue,
// c5 is not returned as an rvalue. Instead, the copy is passed
// back.
//
// The other thing I noticed is that the memory address of the
// character also changes. This I guess makes sense because the
// move constructor is invoked. This is probably another question
// for another blog post.
}

/**
 * In conclusion, I think that rvalue references are a really deep topic- much
 * more difficult than lvalue references. It seems as though each time I write
 * a blog post, more questions than answers are produced. Next time I will write
 * some small examples showing how to use rvalue's in class methods. Thanks
 * for reading!
 */

/**

REFERENCES

// I normally don't remark in the references, but READ THIS BLOG ENTRY, it's
// really good. Enjoyed it a lot.
[1] - http://akrzemi1.wordpress.com/2011/08/30/move-constructor-qa


*/

/**

RESULTS

1) DEFAULT CONSTRUCTOR
Character: Default Constructor

2) COPY CONSTRUCTOR
Character: Copy Constructor

3) ASSIGNMENT BY VALUE
Character: Copy Constructor

4) ASSIGNMENT
Character: Default Constructor
Character: Assignment Operator
Character: Copy Constructor

5) ASSIGNMENT BY VALUE
Character: Default Constructor
ReturnByValue

8) ASSIGNMENT BY RVALUE FROM ReturnByValue()
Character: Default Constructor
ReturnByValue

10) PASS AND RETURN RVALUE
    c5.age = 32
    c5.name = JR
Character: Copy Constructor
EchoByValue
    &temporary = -1075554720
Character: Move Constructor
    c5.age = 32
    c5.name = JR
    &c10 = -1075554696
    c10.age = 33
    c10.name = JR

*/

No comments:

Post a Comment