C++ Object Copying
Tags: programming.c++
C++ is quite a different beast of a language to, say, Java or Python.
My early programming experience was in the latter, so I find my ‘mental model’
for reasoning about what C++ does with the code I write is sometimes wrong.
One area where I make mistakes is underestimating where C++ will make copies of
objects.
That Java/Python are (largely) pass-by-reference, and C++ is pass-by-value
(unless otherwise specified) is prob’ly what drives that. – Another way of
saying this is that when “passing objects around”, Java/Python tend to only
copy pointers to objects, whereas C++ (by default) usually makes copies of
objects.
It may be helpful to illustrate this, to see where C++ copies things. (Or, perhaps it may be easier to remember where C++ doesn’t copy things).
Here’s one I prepared earlier. (The gist program contains a good output listing for the snippets below).
A Custom Object
Consider a custom object MyObj:
class MyObj {
public:
MyObj(int x) {
cout << " CONS MyObj(" << x << ")" << endl;
x_ = x;
}
~MyObj() {
cout << " DEST MyObj(" << x_ << ")" << endl;
}
MyObj(const MyObj& other) {
x_ = other.x();
cout << " CPY Copying from " << x_<< endl;
}
MyObj& operator=(const MyObj& rhs) {
cout << " ASSG Assigning from " << rhs.x() << " to " << x_ << endl;
if (this == &rhs) return *this;
x_ = rhs.x();
return *this;
}
void setX(int x) { x_ = x; }
int x() const { return x_; }
private:
int x_;
};MyObj is a straightforward class; we provide a constructor, destructor,
copy-constructor and assignment operator which each output when they’re called.
In Initialisation
MyObj x0 = MyObj(10); // A1
MyObj x1(5); // A2
MyObj x2(x1); // A3
MyObj x3 = x1; // A4A1Only calls the constructor; not the copy-constructor or assignment operator.A2is the same asA1.A3is an explicit call to the copy-constructor.A4is also a call to the copy-constructor. (i.e. same as (A3)).
In Assignment
x2 = x3; // B1B1is a call to the assignment operator.
The copy-constructor isn’t invoked in the assignment operator (in this example).
In Function Calls
Consider functions:
void outp(MyObj o) {
cout << " Output MyObj(" << o.x() << ")" << endl;
}
void outpC(const MyObj& o) {
cout << " Output MyObj(" << o.x() << ")" << endl;
}and the snippet:
outp(x1); // C1
outpC(x1); // C2C1, a call tooutpinvokes a copy.C2, whereoutpCtakes a constant-reference, does not invoke a copy.
Assigning to References
const MyObj& crx0 = x0; // D1
MyObj& rx0 = x0; // D2D1doesn’t invoke a copy.D2doesn’t invoke a copy.
Assigning to references doesn’t invoke copies.
Results of Functions
Consider the function:
MyObj f1() {
cout << " f1():" << endl;
return MyObj(7);
}and the snippet:
MyObj x4 = f1(); // E1
const MyObj& crx4 = f1(); // E2
MyObj& xr4 = f1(); // E3both CONS no cpy, except 3 which doesn’t compile
E1doesn’t invoke a copy.- I think this is the compiler optimising a way a destruct of the value from the function, and a copy-constructor to the tmp value.
E2also doesn’t invoke a copy.E3doesn’t compile; that’s because a reference must point to an ‘lvalue’ (something which can be referred to by name).- Apparently C++11 allows rvalue references (
&&), which allows ‘move semantics’.
- Apparently C++11 allows rvalue references (
Pointer Dereferencing
MyObj* ptr = new MyObj(13); // F1
MyObj deref = *ptr; // F2
MyObj& derefRef = *ptr; // F3
const MyObj& constDeref = *ptr; // F4
delete ptr; // F5F1is a call to the constructor, (andF5to the destructor).F2invokes the copy constructor.F3doesn’t invoke a copy.- Pointer dereferencing doesn’t necessarily demand a copy.
The dereference of*thisinMyObj& operator=(...)also demonstrates that.
- Pointer dereferencing doesn’t necessarily demand a copy.
F4doesn’t invoke a copy, either.