Sunday, May 5, 2013

Returning rvalue's from Methods


/**
 * The following post is all about the new ref-qualifiers in C++11. I had
 * some troubles while using g++ 4.7 that I will impart to you. My trusty
 * g++ compiler (4.7) could not compile this example. I was puzzled- being
 * new to the rvalue reference stuff I thought it was a syntax error in
 * what I was doing. It's not a syntax error! Remember in an older post
 * I mentioned that g++ did not fully implement C++11? This is one of those
 * cases [1].
 *
 * In a divergent move, I installed the clang compiler [2]. Clang is a [new]
 * open source compiler architecture. I'll spare you the details about the
 * implementation because its website already does a great job. Developers
 * can build clang from source OR if your distro allows it: install from
 * a package manager.
 *
 * On Ubuntu just do the following:
 *
 * sudo apt-get install clang
 *
 * And clang++ will be installed. Clang can handle the program below. I
 * know at work Clang is gradually building a reputation as a solid
 * piece of software. So far I have no complaints (other than lack of
 * documentation on programmatically using it, but that is a different
 * story).
 */

#include <iostream>
#include <string>


class A
{
public:
A() { std::cout << "A()\n" << std::endl; }
A(A const&) = delete;
A& operator=(A const&) = delete;
A(A&& a) { std::cout << "A(A&& a)" << std::endl; }

~A() { std::cout << "~A()" << std::endl; }

};


A&& Create1()
{
A a;
return std::move(a);
}


/**
 * This is a story about a class named A. A could only be created
 * with a single constructor and allowed to move using move semantics.
 * The following program has a fatal flaw: we end up with a dangling
 * reference. The program will compile and run, but notice the
 * order of creation/deletion/and use.
 */
int main(int argc, char** argv)
{
A&& a1 = Create1();
std::cout << "Try to use a1" << std::endl;
}

/**
 * The result is:
 *
 * A()
 * ~A()
 * Try to use a1
 *
 *
 * Uh-oh. A seemingly innocent use of the rvalue ends catastrophically.
 * The reason is that inside of Create1() we create a temporary object
 * called "a" and we try to move it. However the compiler will destroy
 * the temporary object. That's not a good feeling.
 *
 */


/**
 * So this is the second attempt at using the rvalue reference returned
 * from a function.
 */

#include <iostream>
#include <string>


class A
{
public:
A() { std::cout << "A() - " << reinterpret_cast<int>(this) << std::endl; }
A(A const&) = delete;
A& operator=(A const&) = delete;
A(A&& a) { std::cout << "A(A&& a) - " << reinterpret_cast<int>(this) << std::endl; }

~A() { std::cout << "~A() - " << reinterpret_cast<int>(this) << std::endl; }

};


/**
 * This is where we make the change. Instead of returning by rvalue ref,
 * just return by value.
 */
A Create1()
{
A a;
return std::move(a);
}


/**
 * This is the sequel to the original program.
 */
int main(int argc, char** argv)
{
A&& a1 = Create1();
std::cout << "Try to use a1 - " << reinterpret_cast<int>(&a1) << std::endl;
}

/**
 * The result is:
 *
 * A() - -1078920160
 * A(A&& a) - -1078920096
 * ~A() - -1078920160
 * Try to use a1 - -1078920096
 * ~A() - -1078920096
 *
 * This is now safe, plus we see our move constructor used! We first create
 * a temporary A inside of Create1(). Then std::move(a) will end up creating
 * a new object using the move constructor. The destructor fires on the
 * original object "a" because we are exiting Create1(). We then use the
 * move constructed version of a1. Finally that version is deleted.
 *
 * I guess the conclusion I'm drawing from this is that the compiler must
 * analyze the call itself to figure out whether or not it will return an
 * rvalue reference. The final question I have is what happens if the item
 * return type is a value?
 */
/**
 * So this is the third attempt at using the rvalue reference returned
 * from a function.
 */

#include <iostream>
#include <string>


class A
{
public:
A() { std::cout << "A() - " << reinterpret_cast<int>(this) << std::endl; }
A(A const&) { std::cout << "A(A const&) - " << reinterpret_cast<int>(this) << std::endl; }
A& operator=(A const&) { std::cout << "A& operator=(A const&) - " << reinterpret_cast<int>(this) << std::endl; return *this; }
A(A&& a) { std::cout << "A(A&& a) - " << reinterpret_cast<int>(this) << std::endl; }

~A() { std::cout << "~A() - " << reinterpret_cast<int>(this) << std::endl; }

};


/**
 * This is where we make the change. Instead of returning by rvalue ref,
 * just return by value.
 */
A Create1()
{
A a;
return std::move(a);
}


/**
 * This is the sequel to the original program.
 */
int main(int argc, char** argv)
{
A a2 = Create1();
std::cout << "Try to use a2 - " << reinterpret_cast<int>(&a2) << std::endl;
}

/**
 * The result is:
 *
 * A() - -1080270912
 * A(A&& a) - -1080270856
 * ~A() - -1080270912
 * Try to use a2 - -1080270856
 * ~A() - -1080270856
 *
 * Interestingly enough, it's the same exact run order as before! This is kind
 * of a big deal- say you have a program using C++03 where you are using the
 * form of construction similar to constructing a2 (by value). By simply moving
 * to C++11 and return std::move(obj) from the method, it would seem that we
 * could get a little bit of a performance increase. I guess this would also be
 * contingent upon implementing the move semantics.
 */


/**

REFERENCES

[1] - http://gcc.gnu.org/projects/cxx0x.html

[2] - http://clang.llvm.org

*/

No comments:

Post a Comment