"lvalueness versus rvalueness is a property of expressions, not of objects"
"Lvalue references and rvalue references are distinct types."
functions returning by value should return Type (like strange()) instead of const Type (like charm()). The latter buys you virtually nothing (forbidding non-const member function calls) and prevents the move semantics optimization.
- Move constructors and move assignment operators are never implicitly declared.
- The implicit declaration of a default constructor is inhibited by any user-declared constructors, including copy constructors and move constructors.
- The implicit declaration of a copy constructor is inhibited by a user-declared copy constructor, but not a user-declared move constructor.
- The implicit declaration of a copy assignment operator is inhibited by a user-declared copy assignment operator, but not a user-declared move assignment operator.
Basically, the automatic generation rules don't interact with move semantics, except that declaring a move constructor, like declaring any constructor, inhibits the implicitly declared default constructor.
Remember from C++98/03 that named lvalue references are lvalues (if you say int& r = *p; then r is an lvalue) and unnamed lvalue references are also lvalues (given vector v(10, 1729), calling v[0] returns int& , an unnamed lvalue reference whose address you can take).
Rvalue references behave differently:
- Named rvalue references are lvalues.
- Unnamed rvalue references are rvalues.
#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; }
#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, a); inner(b, b); inner(3, 3); inner(a, b); inner(b, a); inner(a, 3); inner(3, a); inner(b, 3); inner(3, b); cout << endl << "Calling outer()." << endl; outer(a, a); outer(b, b); outer(3, 3); outer(a, b); outer(b, a); outer(a, 3); outer(3, a); outer(b, 3); outer(3, b); }
rvalue references: template argument deduction and reference collapsing
Rvalue references and templates interact in a special way. This demonstrates what happens:
#include <iostream> #include <ostream> #include <string> using namespace std; template <typename T> struct Name; template <> struct Name<string> { static const char * get() { return "string"; } }; template <> struct Name<const string> { static const char * get() { return "const string"; } }; template <> struct Name<string&> { static const char * get() { return "string&"; } }; template <> struct Name<const string&> { static const char * get() { return "const string&"; } }; template <> struct Name<string&&> { static const char * get() { return "string&&"; } }; template <> struct Name<const string&&> { static const char * get() { return "const string&&"; } }; template <typename T> void quark(T&& t) { cout << "t: " << t << endl; cout << "T: " << Name<T>::get() << endl; cout << "T&&: " << Name<T&&>::get() << endl; cout << endl; } string strange() { return "strange()"; } const string charm() { return "charm()"; } int main() { string up("up"); const string down("down"); quark(up); quark(down); quark(strange()); quark(charm()); }
C++0x transforms both the function parameter type and the function argument type before matching them together.
- It transforms the function argument type. A special rule is activated (N2798 14.8.2.1 [temp.deduct.call]/3):
-
when the function parameter type is of the form T&& where T is a template parameter, and the function argument is an lvalue of type A, the type A& is used for template argument deduction.
(This special rule doesn't apply to function parameter types of the form T& or const T& , which behave as they did in C++98/03, nor does it apply to const T&& .)
It transforms the function parameter type. Both C++98/03 and C++0x discard references (C++0x discards both lvalue references and rvalue references). In all four cases, this means that we transform T&& into T .
References to references are collapsed in C++0x, and the reference collapsing rule is that "lvalue references are infectious". X& & , X& && , and X&& & collapse to X& . Only X&& && collapses to X&& . So, string& && collapses to string& . In templates, things that look like rvalue references aren't necessarily so.
So, in Template argument deduction, T&& can accecpt
- Lvalue -- Special deduction
- const Lvalue -- Special deduction
- Rvalue
- const Rvalue
perfect forwarding: how std::forward() and std::identity work
Fortunately, unnamed lvalue references are lvalues, and unnamed rvalue references are rvalues. So, in order to forward t1 and t2 to inner(), we need to pass them through helper functions that preserve their types but remove their names. This is what std::forward() does:
template <typename T> struct Identity { typedef T type; }; template <typename T> T&& Forward(typename Identity<T>::type&& t) { return t; }
When we call Forward
what would happen if you accidentally wrote Forward
What is Identity doing? Why wouldn't this work:
template <typename T> T&& Forward(T&& t) { // BROKEN return t; }
If Forward() were written like that, it could be called without explicit template arguments. Template argument deduction would kick in, and we've seen what happens to T&& - it becomes an lvalue reference when its function argument is an lvalue. And the whole problem that we're trying to solve with Forward() is that within outer(), the named t1 and t2 are lvalues even when their types T1&& and T2&& are rvalue references. With the BROKEN implementation, Forward
Reference:
Rvalue References: C++0x Features in VC10, Part 2
Lambdas, auto, and static_assert: C++0x Features in VC10, Part 1
MSDN : Lvalues and Rvalues
How to: Write a Move Constructor
Rvalue Reference 與 String 的實作
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.