The original text address is here. For more information, see the source. New Feature of C ++ 0x in VC10: Right Value reference (rvalue references) (1) Move semantics: Move the left ValueWhat if you like to use the assign operator to implement the copy constructor? You may try to use your move assignment operator to implement the move constructor. This is possible, but you need to be careful. Below isErrorMethod:
C:/temp> type unified_wrong.cpp # Include <stddef. h> # Include <iostream> # Include <ostream> Using namespace STD; Class remote_integer { Public: Remote_integer (){ Cout <"default constructor." <Endl; M_p = NULL; } Explicit remote_integer (const int N ){ Cout <"unary constructor." <Endl; M_p = new int (N ); } Remote_integer (const remote_integer & Other ){ Cout <"copy constructor." <Endl; M_p = NULL; * This = Other; } # Ifdef movable Remote_integer (remote_integer & Other ){ Cout <"Move constructor." <Endl; M_p = NULL; * This = Other; // wrong } # Endif // # ifdef movable Remote_integer & operator = (const remote_integer & Other ){ Cout <"Copy assignment operator." <Endl; If (this! = & Other ){ Delete m_p; If (other. m_p ){ M_p = new int (* Other. m_p ); } Else { M_p = NULL; } } Return * this; } # Ifdef movable Remote_integer & operator = (remote_integer & Other ){ Cout <"Move assignment operator." <Endl; If (this! = & Other ){ Delete m_p; M_p = Other. m_p; Other. m_p = NULL; } Return * this; } # Endif // # ifdef movable ~ Remote_integer (){ Cout <"destructor." <Endl; Delete m_p; } Int get () const { Return m_p? * M_p: 0; } PRIVATE: Int * m_p; }; Remote_integer frumple (const int N ){ If (n = 1729 ){ Return remote_integer (1729 ); } Remote_integer RET (N * n ); Return ret; } Int main (){ Remote_integer x = frumple (5 ); Cout <X. Get () <Endl; Remote_integer y = frumple (1729 ); Cout <Y. Get () <Endl; } C:/temp> Cl/ehs/ nologo/W4/O2 uniied_wrong.cpp Unified_wrong.cpp C:/temp> unified_wrong Unary constructor. Copy constructor. Copy assignment operator. Destructor. 25 Unary constructor. 1729 Destructor. Destructor. C:/temp> Cl/ehs/ nologo/W4/O2/dmovable unified_wrong.cpp Unified_wrong.cpp C:/temp> unified_wrong Unary constructor. Move constructor. Copy assignment operator. Destructor. 25 Unary constructor. 1729 Destructor. Destructor. |
(The Compiler implements RVO optimization here, but there is no nrvo. As I have mentioned above, some calls to copy constructors have been optimized by RVO and nrvo, but the compiler does not implement this optimization every time, and the move constructor has optimized the rest) The line marked with wrong in the move constructor calls the copy assignment operator instead of the move assignment operator! This can also be compiled and run normally, but the purpose of moving the constructor is not achieved. Why? Recall from C ++ 98/03 that the named left value reference is the left value (Int & R = * P;, r is the left value) anonymous left value reference or left value (vector <int> V (10,172 9), call V [0] To return Int &. This is an anonymous left value reference, its address can be obtained ). The behavior of the right value reference is different:
- The specified right value reference is the left value.
- The anonymous right value reference is the right value.
The specified left value reference can be used repeatedly or multiple operations can be performed on the reference. If you enable the name to reference the left value as the right value, the first operation that is applied to the name may steal its resources, leading to the failure of subsequent operations. Stealing should not affect other operations. Therefore, the reference to the left value of the name should be the left value. On the other hand, anonymous right value reference is not reused, so it can keep its own right value attribute. If you really want to use the move assignment operator to implement the move constructor, you need the ability to regard the left value as the right value. STD: Move () in the C ++ 0x header file <utility> gives you this capability, and VC10 will include it. STD: Move () was not included in VC10 at the time of original writing, so the author then gave the implementation of STD: Move)
C:/temp> type unified_right.cpp # Include <stddef. h> # Include <iostream> # Include <ostream> Using namespace STD; Template <typename T> struct removereference { Typedef T type; }; Template <typename T> struct removereference <t &> { Typedef T type; }; Template <typename T> struct removereference <t &> { Typedef T type; }; Template <typename T> typename removereference <t>: Type & move (T & T ){ Return T; } Class remote_integer { Public: Remote_integer (){ Cout <"default constructor." <Endl; M_p = NULL; } Explicit remote_integer (const int N ){ Cout <"unary constructor." <Endl; M_p = new int (N ); } Remote_integer (const remote_integer & Other ){ Cout <"copy constructor." <Endl; M_p = NULL; * This = Other; } # Ifdef movable Remote_integer (remote_integer & Other ){ Cout <"Move constructor." <Endl; M_p = NULL; * This = move (other); // right } # Endif // # ifdef movable Remote_integer & operator = (const remote_integer & Other ){ Cout <"Copy assignment operator." <Endl; If (this! = & Other ){ Delete m_p; If (other. m_p ){ M_p = new int (* Other. m_p ); } Else { M_p = NULL; } } Return * this; } # Ifdef movable Remote_integer & operator = (remote_integer & Other ){ Cout <"Move assignment operator." <Endl; If (this! = & Other ){ Delete m_p; M_p = Other. m_p; Other. m_p = NULL; } Return * this; } # Endif // # ifdef movable ~ Remote_integer (){ Cout <"destructor." <Endl; Delete m_p; } Int get () const { Return m_p? * M_p: 0; } PRIVATE: Int * m_p; }; Remote_integer frumple (const int N ){ If (n = 1729 ){ Return remote_integer (1729 ); } Remote_integer RET (N * n ); Return ret; } Int main (){ Remote_integer x = frumple (5 ); Cout <X. Get () <Endl; Remote_integer y = frumple (1729 ); Cout <Y. Get () <Endl; } C:/temp> Cl/ehs/ nologo/W4/O2/dmovable unified_right.cpp Unified_right.cpp C:/temp> unified_right Unary constructor. Move constructor. Move assignment operator. Destructor. 25 Unary constructor. 1729 Destructor. Destructor. |
(I will regard the move () I implemented as STD: Move (), because the implementation principles are the same.) How does STD: Move () work? Currently, I can only tell you that this is a "magic of ox X ". (I will explain it in detail later. It is not complicated, but it includes template parameter derivation and reference degradation ), I will see these two things when I talk about perfect forwarding later.) I can use a specific example to skip this magic: to give a left value of the string type, like up in the code above, STD: Move (up) calls string & STD: Move (string &), and returns an anonymous right value reference, while the anonymous right value reference is the right value. Given a string type with the right value such as strange () in the code above, STD: Move (strange () calls string & STD :: move (string &), again, whether the returned value is an anonymous right value reference or a right value. STD: move is also useful elsewhere. You can use STD: Move (left value expression) whenever you have a left value and you no longer need it (it will be destroyed or assigned another value) to activate the move semantics. Move semantics: movable data memberStandard C ++ 0x classes (such as vector, string, and RegEx) all have the move constructor and the move assignment operator, we have learned how to manually manage resources in our own classes. But what should we do when our class contains moving data members (vector, String, RegEx? The compiler will not automatically generate the move constructor and the move assignment operator for us. Therefore, we need to implement them by ourselves. Fortunately, with STD: Move (), this is very simple:
C:/temp> type point. cpp # Include <stddef. h> # Include <iostream> # Include <ostream> Using namespace STD; Template <typename T> struct removereference { Typedef T type; }; Template <typename T> struct removereference <t &> { Typedef T type; }; Template <typename T> struct removereference <t &> { Typedef T type; }; Template <typename T> typename removereference <t>: Type & move (T & T ){ Return T; } Class remote_integer { Public: Remote_integer (){ Cout <"default constructor." <Endl; M_p = NULL; } Explicit remote_integer (const int N ){ Cout <"unary constructor." <Endl; M_p = new int (N ); } Remote_integer (const remote_integer & Other ){ Cout <"copy constructor." <Endl; If (other. m_p ){ M_p = new int (* Other. m_p ); } Else { M_p = NULL; } } Remote_integer (remote_integer & Other ){ Cout <"Move constructor." <Endl; M_p = Other. m_p; Other. m_p = NULL; } Remote_integer & operator = (const remote_integer & Other ){ Cout <"Copy assignment operator." <Endl; If (this! = & Other ){ Delete m_p; If (other. m_p ){ M_p = new int (* Other. m_p ); } Else { M_p = NULL; } } Return * this; } Remote_integer & operator = (remote_integer & Other ){ Cout <"Move assignment operator." <Endl; If (this! = & Other ){ Delete m_p; M_p = Other. m_p; Other. m_p = NULL; } Return * this; } ~ Remote_integer (){ Cout <"destructor." <Endl; Delete m_p; } Int get () const { Return m_p? * M_p: 0; } PRIVATE: Int * m_p; }; Class remote_point { Public: Remote_point (const int x_arg, const int y_arg) : M_x (x_arg), m_y (y_arg ){} Remote_point (remote_point & other) : M_x (move (other. m_x )), M_y (move (other. m_y )){} Remote_point & operator = (remote_point & Other ){ M_x = move (other. m_x ); M_y = move (other. m_y ); Return * this; } Int X () const {return m_x.get ();} Int y () const {return m_y.get ();} PRIVATE: Remote_integer m_x; Remote_integer m_y; }; Remote_point five_by_five (){ Return remote_point (5, 5 ); } Remote_point taxicab (const int N ){ If (n = 0 ){ Return remote_point (1, 1728 ); } Remote_point RET (729,100 0 ); Return ret; } Int main (){ Remote_point P = taxicab (43112609 ); Cout <"(" <p. x () <"," <p. Y () <")" <Endl; P = five_by_five (); Cout <"(" <p. x () <"," <p. Y () <")" <Endl; } C:/temp> Cl/ehs/ nologo/W4/O2 point. cpp Point. cpp C:/temp> point Unary constructor. Unary constructor. Move constructor. Move constructor. Destructor. Destructor. (729,100 0) Unary constructor. Unary constructor. Move assignment operator. Move assignment operator. Destructor. Destructor. (5, 5) Destructor. Destructor. |
As you can see, it is very complicated to move each data member one by one. Note that the move assignment operator of remote_point does not perform the auto-Assignment Check because remote_integer already does. Note that the copy constructor, value assignment operator, and destructor declared by remote_point implicitly complete the corresponding functions. Last topic: You should try your best to implement the move constructor and the move assignment operator for your copyable classes, because the compiler will not generate them for you. In this way, not only can we benefit from moving semantics when using these classes at ordinary times, but STL containers and general algorithms can also benefit from moving semantics because they can replace expensive replication with moving. Forwarding ProblemsIn C ++ 98/03, the left, right, reference, and template rules look perfect. The problem occurs until the programmer tries to write some highly generalized code. Suppose you want to write a fully generalized function outer (), which aims to obtain any number or type of parameters and then forward them to the function inner (). There are already some good solutions, such as the factory method make_shared <t> (ARGs) to forward the ARGs to the T constructor, and return a shared_ptr <t>. (In this way, the T-type object and its reference count are stored in the same memory block, which is as efficient as the intrusive reference count) as function <RET (ARGs)> such a packaging class can pass parameters to function objects stored inside it, and so on. In this article, we are only interested in forwarding parameters to inner () by outer. There is another problem as to how the return value type of outer () can be inferred. (For example, make_shared <t> (ARGs) always returns shared_ptr <t>. To completely solve this problem, we need to use the C ++ 0x feature decltype) If there is no parameter, we will not discuss it. When the parameter is a case, let's try to write outer ():
Template <typename T> void outer (T & T ){ Inner (t ); } |
The problem with this outer () is that it cannot forward right-value parameters. If inner () accepts the const Int &, inner (5) parameter, but outer (5) cannot compile, t will be deduced as int, and Int & cannot be bound to 5. Okay. Let's try again:
Template <typename T> void outer (const T & T ){ Inner (t ); } |
In this way, if Inner () accepts the parameter 'int', it violates the constant constraint and thus compilation fails. If you can load T & Const T & respectively, outer () does work, and then you can use outer () Just like inner (). Unfortunately, when parameters increase, you need to write a lot of heavy-load functions: T1 & and const T1 &, T2 & and const T2 &... and so on. An increase in each parameter will result in an exponential increase in the number of heavy loads. (STD: tr1: BIND () function in vc9 SP1 has done this for the first five parameters, including 63 overload forms. Otherwise, you have to explain to the user why the function object cannot be bound with a right-value parameter such as 1729. In order to generate these overload functions, we need to use an annoying preprocessing mechanism, so you don't want to touch it) The forwarding problem is serious in C ++ 98/03 and cannot be solved in essence (besides the use of the annoying preprocessing mechanism, it will significantly reduce the compilation speed, and cause extremely poor code readability ). However, the right value reference elegantly solves this forwarding problem. (I have explained initialization and reload determination before interpreting the move semantic model, but now I will first explain the perfect forwarding model, and then explain the template type derivation and reference degradation rules. This seems better) Perfect forwarding: ModelPerfect forwarding allows you to write only one function template to accept N parameters of any type and transparently forward them to any function. Their attributes of constants, variables, left values, and right values are retained, allowing you to use outer () Like inner (), it can also work with the move semantics to obtain additional benefits (the variable-length template parameter of C ++ 0x solves the problem of "any number of parameters, we can regard N as any number ). It may be a little magical, but it is actually very simple:
C:/temp> type perfect. cpp # Include <iostream> # Include <ostream> Using namespace STD; Template <typename T> struct identity { Typedef T type; }; Template <typename T> T & forward (typename identity <t >:: type & T ){ Return T; } Void inner (Int &, Int &){ Cout <"inner (Int &, Int &)" <Endl; } Void inner (Int &, const Int &){ Cout <"inner (Int &, const Int &)" <Endl; } Void inner (const Int &, Int &){ Cout <"inner (const Int &, Int &)" <Endl; } Void inner (const Int &, const Int &){ Cout <"inner (const Int &, const Int &)" <Endl; } Template <typename T1, typename T2> void outer (T1 & T1, T2 & T2 ){ Inner (Forward <t1> (T1), forward <t2> (T2 )); } Int main (){ Int A = 1; Const int B = 2; Cout <"directly calling inner ()." <Endl; Inner (A, ); Inner (B, B ); Inner (3, 3 ); Inner (A, B ); Inner (B, ); Inner (A, 3 ); Inner (3, ); Inner (B, 3 ); Inner (3, B ); Cout <Endl <"calling outer ()." <Endl; Outer (A, ); Outer (B, B ); Outer (3, 3 ); Outer (A, B ); Outer (B, ); Outer (A, 3 ); Outer (3, ); Outer (B, 3 ); Outer (3, B ); } C:/temp> Cl/ehs/ nologo/W4 perfect. cpp Perfect. cpp C:/temp> perfect Directly calling inner (). Inner (Int &, Int &) Inner (const Int &, const Int &) Inner (const Int &, const Int &) Inner (Int &, const Int &) Inner (const Int &, Int &) Inner (Int &, const Int &) Inner (const Int &, Int &) Inner (const Int &, const Int &) Inner (const Int &, const Int &) Calling outer (). Inner (Int &, Int &) Inner (const Int &, const Int &) Inner (const Int &, const Int &) Inner (Int &, const Int &) Inner (const Int &, Int &) Inner (Int &, const Int &) Inner (const Int &, Int &) Inner (const Int &, const Int &) Inner (const Int &, const Int &) |
So handsome! To achieve perfect forwarding, you only need to write two lines! The code above demonstrates how to transparently forward t1 and t2 to inner (); Inner () can know their left value, right value, and constant, just as it is called directly. Like STD: Move (), STD: identity and STD: Forward () are defined in the <utility> header file of C ++ 0x (which will be included in VC10 ); I have demonstrated how they are implemented. (Next I will use STD: identity and self-implemented identity, STD: Forward () and forward as they are implemented in the same way) Next, let's reveal the secret of magic. In fact, it relies on the template parameter derivation and reference folding technology. |