This would be more of a real world example:
Constructors now do something useful.Code:#include "stdafx.h" #include <iostream> #include <utility> #include <ctime> #include <cstdlib> class A { public: A(int n) { std::cout << "A: Constructing...\n"; m_n = n; m_p = new int[1000]; } A(A& rhs) { std::cout << "A: Copy constructing...\n"; m_p = new int[1000]; std::copy(rhs.m_p, rhs.m_p + 1000, m_p); m_n = rhs.m_n; } A(A&& rhs) { std::cout << "A: Move constructing...\n"; m_p = rhs.m_p; rhs.m_p = nullptr; m_n = rhs.m_n; } ~A() { std::cout << "A: Destructing...\n"; delete [] m_p; } int m_n; int* m_p; }; A&& foo() { static A test(std::rand()); return std::move(test); } A bar() { A test(std::rand()); return test; } int main() { std::srand(std::time(nullptr)); { std::cout << "Test with foo:\n"; A tmp = foo(); std::cout << "Result: " << tmp.m_n << std::endl; } { std::cout << "\nTest with bar:\n"; A tmp = bar(); std::cout << "Result: " << tmp.m_n << std::endl; } { std::cout << "\nTest with foo (r-ref):\n"; A&& tmp = foo(); std::cout << "Result: " << tmp.m_n << std::endl; } { std::cout << "\nTest with bar (r-ref):\n"; A&& tmp = bar(); std::cout << "Result: " << tmp.m_n << std::endl; } }
(Although if this isn't optimized out by the compiler isn't certain; yet it is only there for demonstration and has no real point, so that's fine.)
The mechanism of "move semantics" in place doesn't say "you don't have to copy this datum an extra time". What has been in place is a means to distinguish between a call to a copying operation and a call to a moving operation. The point of "r-value references" is to allow "destructive copies".you can certainly invoke the move semantics with any kind of return, but only a returned pointer or reference of some sort will eliminate the extra copy operation.
All of the attempts you are making to circumvent the copy operation, in the face of C++11 and "move semantics", is flawed because the overloaded constructor determines the best course of action. This determination is made at the best possible point. That is why "move semantics" for C++ is such a big deal. You don't need to allocate space for an object and return a pointer. You don't have to return a smart pointer. You don't need to make the object static. (These are "okay" alternatives, but they have consequences.) Other alternatives are manually activated move semantics, or variations on "pimpl". These are all options that existed before C++11.
If the object represents a, naively determined, expensive to copy object the possibility of copying only a few primitives around instead represents the supposed benefit, but that's hardly the end of it. (That's not nearly as often the case as some would have you believe.) The benefit comes when the constructor, copy constructor, move constructor, and destruction are written with that possibility in mind. With the chosen "move semantics", you don't even need to fully construct a temporary; you only need to put the object to be destroyed in a state that the destructor can quickly deal with. Further, with a little work, you can make every "lifetime" operation but a couple of constructors and one path in the destructor use only primitive operations which can't fail by definition.
Soma
And a perfect example of why programmers shouldn't trivialize functions returning references to local variable even if made static.Constructors now do something useful.
This line
effectively destroys the `foo::test' `A' instance.Code:A tmp = foo();
Soma
Thank you very much for all your posts, 19 posts in such a short time. I am not too sure what happened with my code when I was testing it for the supposed "incorrect case" as laserlight pointed out. I was getting a NULL object as a result for one of my tests, but I probably did some simple mistake like using "Str&" as the return type. Laserlight also mentioned the possibility of using the "<<" operator, which I commonly use, but am a little confused on how to implement such an overload without editing the Str library's file.
But what also seems interesting is the copy and move constructors. I have never run across those before, but they seem pretty interesting. I am assuming that each class have a built in default mover/copier? Or am I mistaken? The only way I did copying in the past was to overload the operator= to do my own little copying method.
Thanks again for all the great support.
Last edited by jakebird451; 08-31-2011 at 05:16 PM. Reason: changed to actual post count
This function is also horribly broken.Code:A&& foo() { static A test(std::rand()); return std::move(test); }
1) If your test program shows two different values from foo, that's because your constructors don't initialize the data members. Foo always returns A with the same random value, unlike bar.
2) It also says that the function is done with the instance `test` and is happy if you decide to move from it (i.e leaving it in an unspecified state). Which means that only the first invocation may have a meaningful result.
E.g, if the result is used like this:
then it invokes the move constructor which is free to invalidate the right-hand object (foo::test).Code:A a = foo();
This should be the correct way (except that rand is called just once). The inclusion of rvalue references doesn't mean that normal references suddenly stop doing their job.Code:A& foo() { static A test(std::rand()); return test; }
Nor does it mean that you should abuse rvalue references for performance, sacrificing code correctness.
Last edited by anon; 09-01-2011 at 01:02 AM.
I might be wrong.
Quoted more than 1000 times (I hope).Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.
If I'm not mistaken, you get default move constructors under certain conditions: no user-defined copy constructor + no user-defined destructor + all members are movable, or something like that. (This has been rather volatile in the drafts, I think.)
I might be wrong.
Quoted more than 1000 times (I hope).Thank you, anon. You sure know how to recognize different types of trees from quite a long way away.