/**
* 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
*/
Sunday, May 5, 2013
Returning rvalue's from Methods
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment