拷贝构造函数与移动构造函数的区别及相关代码疑问咨询
Hey there! Let's unpack your questions one by one—first clarifying the difference between copy and move constructors with examples, then diving into why your code behaves the way it does.
Let's start with the core purpose and behavior of each:
Copy Constructor
- What it does: Creates a new object that's an exact, independent copy of an existing object. It works with lvalues (objects that have a persistent identity, like a named variable).
- Signature: Typically takes a
constlvalue reference:ABC(const ABC& other)(theconstis crucial for flexibility—we'll come back to this!). - Cost: Copies all resources (like dynamic memory) from the original object, which can be expensive for large objects.
Move Constructor
- What it does: Transfers ownership of resources from a temporary object (rvalue) to a new object. Temporary objects are short-lived (like the return value of a function) and are about to be destroyed anyway.
- Signature: Takes an rvalue reference:
ABC(ABC&& other)(the&&denotes an rvalue reference). - Cost: No resource copying—just "takes over" the original object's pointers/resources, then nulls out the temporary to avoid double destruction. This is vastly more efficient.
Example Code
Here's a concrete example using a class with dynamic memory to show the difference:
#include <iostream> #include <cstring> using namespace std; class MyString { private: char* data; size_t len; public: // Basic constructor MyString(const char* str = "") { len = strlen(str); data = new char[len + 1]; strcpy(data, str); cout << "Basic Constructor called\n"; } // Copy Constructor MyString(const MyString& other) { len = other.len; data = new char[len + 1]; // Allocate new memory strcpy(data, other.data); // Copy actual data cout << "Copy Constructor called\n"; } // Move Constructor MyString(MyString&& other) noexcept { // Take over the existing resources len = other.len; data = other.data; // Null out the temporary object to prevent double free other.len = 0; other.data = nullptr; cout << "Move Constructor called\n"; } // Destructor ~MyString() { if (data) { delete[] data; cout << "Destructor called\n"; } } }; int main() { // Use copy constructor: copy from a named lvalue MyString str1("Hello"); MyString str2 = str1; // Use move constructor: take resources from a temporary rvalue MyString str3 = MyString("World"); return 0; }
Sample Output:
Basic Constructor called Copy Constructor called Basic Constructor called Move Constructor called Destructor called Destructor called Destructor called
Let's break down why ABC obj1 = fun123(); uses the move constructor (or skips it entirely with NRVO):
First, let's note a critical detail in your code: your copy constructor has the signature ABC(ABC& obj) (no const). This means it cannot accept rvalues (like the temporary object returned by fun123()), since rvalues can't bind to non-const lvalue references.
Now, let's cover two scenarios:
1. When NRVO is Enabled (Default for Most Compilers)
NRVO stands for Named Return Value Optimization. It's a compiler optimization that eliminates the need for copy/move operations when returning a named object from a function. Instead of constructing the object in fun123() and then copying/moving it to obj1, the compiler directly constructs the object in main()'s obj1 memory space.
In this case, you'll only see:
Constructor Destructor
No copy or move constructor is called—they're optimized away entirely.
2. When NRVO is Disabled (or Not Applied)
If you disable optimization (e.g., with g++ -fno-elide-constructors), here's what happens:
fun123()constructsobj(outputs "Constructor").fun123()returns a temporary copy ofobj—but since your copy constructor can't accept the rvalue temporary, the compiler looks for a move constructor.- If your move constructor is uncommented, it's called (outputs "Move Constructor") to transfer the temporary's resources to
obj1. - The temporary object is destroyed (outputs "Destructor"), then
obj1is destroyed at the end ofmain()(another "Destructor").
Why You Expected Copy Constructor
You probably assumed the temporary would use the copy constructor, but your non-const copy constructor signature blocks that. If you modify the copy constructor to ABC(const ABC& obj), then when NRVO is disabled, the copy constructor would be called instead—but the move constructor is still preferred for rvalues if it exists, since it's more efficient.
Modified Code to Test
Try this version with a const copy constructor to see the difference:
#include <iostream> using namespace std; class ABC { public: const char *a; ABC() { cout<<"Constructor"<<endl; } ABC(const char *ptr) { cout<<"Constructor"<<endl; } ABC(const ABC &obj) { cout<<"copy constructor"<<endl;} // Added const ABC(ABC&& obj) { cout<<"Move constructor"<<endl; } ~ABC() { cout<<"Destructor"<<endl; } }; ABC fun123() { ABC obj; return obj; } int main() { ABC obj1=fun123(); return 0; }
Compile with g++ -fno-elide-constructors test.cpp and run—you'll see the move constructor is called instead of the copy constructor, since it's the better match for rvalues.
内容的提问来源于stack exchange,提问作者sunshilong369

