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:
(int x) {
MyObj<< " CONS MyObj(" << x << ")" << endl;
cout x_ = x;
}
~MyObj() {
<< " DEST MyObj(" << x_ << ")" << endl;
cout }
(const MyObj& other) {
MyObjx_ = other.x();
<< " CPY Copying from " << x_<< endl;
cout }
& operator=(const MyObj& rhs) {
MyObj<< " ASSG Assigning from " << rhs.x() << " to " << x_ << endl;
cout 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(10); // A1
MyObj x0 (5); // A2
MyObj x1(x1); // A3
MyObj x2= x1; // A4 MyObj x3
A1
Only calls the constructor; not the copy-constructor or assignment operator.A2
is the same asA1
.A3
is an explicit call to the copy-constructor.A4
is also a call to the copy-constructor. (i.e. same as (A3
)).
In Assignment
= x3; // B1 x2
B1
is 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) {
<< " Output MyObj(" << o.x() << ")" << endl;
cout }
void outpC(const MyObj& o) {
<< " Output MyObj(" << o.x() << ")" << endl;
cout }
and the snippet:
(x1); // C1
outp(x1); // C2 outpC
C1
, a call tooutp
invokes a copy.C2
, whereoutpC
takes a constant-reference, does not invoke a copy.
Assigning to References
const MyObj& crx0 = x0; // D1
& rx0 = x0; // D2 MyObj
D1
doesn’t invoke a copy.D2
doesn’t invoke a copy.
Assigning to references doesn’t invoke copies.
Results of Functions
Consider the function:
() {
MyObj f1<< " f1():" << endl;
cout return MyObj(7);
}
and the snippet:
= f1(); // E1
MyObj x4 const MyObj& crx4 = f1(); // E2
& xr4 = f1(); // E3 MyObj
both CONS no cpy, except 3 which doesn’t compile
E1
doesn’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.
E2
also doesn’t invoke a copy.E3
doesn’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
* ptr = new MyObj(13); // F1
MyObj= *ptr; // F2
MyObj deref & derefRef = *ptr; // F3
MyObjconst MyObj& constDeref = *ptr; // F4
delete ptr; // F5
F1
is a call to the constructor, (andF5
to the destructor).F2
invokes the copy constructor.F3
doesn’t invoke a copy.- Pointer dereferencing doesn’t necessarily demand a copy.
The dereference of*this
inMyObj& operator=(...)
also demonstrates that.
- Pointer dereferencing doesn’t necessarily demand a copy.
F4
doesn’t invoke a copy, either.