/**
* 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
*/
Saturday, May 4, 2013
rvalue Reference Moves (Now in Color!)
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment