关于C++11右值引用在Stroustrup示例构造函数中的使用疑问
Great question—let’s unpack this to see why Stroustrup’s original code uses a value parameter, and whether switching to a rvalue reference is a good idea.
First, let’s recap the code snippets for clarity:
Original Code (from the book)
struct Point{ int x,y; }; struct Points{ vector<Point> elem; Points(Point p0){elem.push_back(p0)}; // ... other members }; Point x2{ {100,200} };
Your Proposed Change
struct Points{ vector<Point> elem; Points(Point&& p0){elem.push_back(p0)}; // ... other members };
Why the Original Version Works (and Has Benefits)
The original constructor uses pass-by-value, which has two key advantages:
- Compatibility: It works with both lvalues (like
x2, which has a persistent address you can take with&) and rvalues (like temporary objects such asPoint{1,2}). If you only provide the rvalue reference version, you can’t initialize aPointsinstance with an lvalue likex2—that would trigger a compilation error, since lvalues can’t bind to non-const rvalue references. - Simplicity: For small, trivial types like
Point(which just holds twoints), the cost of copying is negligible. The difference between copying and moving such an object is effectively zero, so optimizing here doesn’t gain you anything. Stroustrup might have chosen this version to keep the example focused on basic constructor mechanics before diving into more complex move semantics.
Issues with Your Rvalue Reference Version
While using a rvalue reference could enable moving p0 into the vector (note: your code still does a copy! Because p0 is a named rvalue reference, which is treated as an lvalue inside the function—you’d need elem.push_back(std::move(p0)); to actually move it), the bigger problem is limited usability. As mentioned, you can’t pass lvalues to this constructor. That’s a major downside unless your only use case for Points is with temporary Point objects, which is unlikely.
A Better Middle Ground (If You Want to Optimize)
If you want to support both lvalues and rvalues while optimizing moves for rvalues, you have a couple of solid options:
Provide Two Overloads:
struct Points{ vector<Point> elem; // Accept lvalues, copy into the vector (safe for values you need to keep using) Points(const Point& p0){elem.push_back(p0)}; // Accept rvalues, move into the vector (avoids copies for temporaries) Points(Point&& p0){elem.push_back(std::move(p0))}; // ... other members };This approach keeps things explicit: lvalues get copied, rvalues get moved, and all valid use cases are supported.
Use Perfect Forwarding:
If you want to handle any type that can be converted toPoint(not justPointitself), you can use a template withstd::forward:struct Points{ vector<Point> elem; template<typename T> Points(T&& p0){elem.push_back(std::forward<T>(p0))}; // ... other members };This forwards the original value category of
p0topush_back, so lvalues are copied and rvalues are moved. Just note that this adds a bit of complexity (like handling unexpected implicit conversions), so it’s best used when you need that flexibility.
Final Takeaway
Your proposed rvalue reference constructor isn’t ideal because it sacrifices compatibility with lvalues for minimal (if any) performance gain with trivial types like Point. The original version is clearer and more flexible, especially for a teaching example. If you do want to optimize move operations, go with the overloaded approach or perfect forwarding instead—they balance performance and usability perfectly.
内容的提问来源于stack exchange,提问作者nodeover

