CPSC 427: Object-Oriented Programming
Michael J. Fischer
Extended Objects, Custody, and the Double Delete Problem
Moving
data
Much of computation involves moving data around.
Almost all non-functional languages support the assignment operator.
Assignment copies a value from the right hand side (RHS) to the
variable on the left hand side (LHS).
After the assignment, there are two copies of the RHS value.
Temporaries
Conceptually, a pure value is a disembodied piece of information floating
in space.
In reality, values always exist somewhere—in variables or in temporary
registers.
Languages such as Java distinguish between primitive values like characters and numbers that can live on the stack, and object values that live in permanent storage and can only be accessed via pointers.
Custody
Primitive values are relatively small. Objects can be large. Objects with
dynamic extensions can be huge, if the size of the extension is taken into
account.
Copying large objects is expensive!
How to avoid copying large objects
Java approach: Assignment copies pointers, not the underlying
object.
To make a copy of the object itself, one creates a new object and
initializes it from the old.
Problem of custody arises: Who is responsible for managing and eventually freeing the storage occupied by large objects and their dynamic extensions?
Java approach
In Java, the custody problem is avoided by letting the system own all
objects and relying on the garbage collector to decide when an object
may be deleted.
This entails a huge performance penalty. Two options, both expensive:
Original C++ approach
The C++ approach is to have the class manage its own dynamic
storage.
The constructor allocates the storage; the destructor deletes it.
The programmer is responsible for ensuring that the storage management is done properly.
Double delete problem
How to do assignment—two approaches:
Move semantics
C++ 11 introduces move semantics.
An object can be moved instead of copied. The data in the source object
is removed from the source object and placed in the target object. The
source object then becomes empty.
In the case of dynamic extensions, the pointer to the extension is
copied from source to target, and the source pointer is set to
nullptr.
Deleting nullptr is a no-op and causes no problems.
We say that custody has been transferred from source to target.
Motivation
A big motivation for move semantics comes from containers such as
vector.
Containers need to be able to move objects around. Old-style containers
can’t work with dynamic extensions.
C++ containers support moving an object into or out of the container.
While in the container, the container has custody of the object.
Move is like a shallow copy, but it avoids the double-delete problem.
Implementation in C++
Here are the changes to C++ that enable move semantics.
Move and copy constructors and assignment operators
Copy and move constructors are distinguished by their prototypes.
class A:
Similarly, copy and move assignment operators have dfiferent prototypes.
class A:
Default constructors and assignment operators
Under some conditions, the system will automatically create default
move and copy constructors and assignment operators.
The default copy constructors and copy assignment operators do a
shallow copy. Data members that are objects of type T are copied
using the copy constructor/assignment operator defined for class
T.
The default move constructors and move assignment operators do a
shallow copy. Data members that are objects of type T are moved
using the move constructor/assignment operator defined for class
T.
Default definitions can be specified or inhibited by use of the keywords =default or =delete.
Moving from a temporary object
A mutable temporary object always has rvalue reference type.
Thus, the following code moves the temporary string created by the on-the-fly constructor string("cat") into the vector v:
Forcing a move from a non-temporary object
The function std::move() in the utility library can be used to force
a move from a non-temporary object.
The following code moves the string in s into the vector v. After the move, s contains the null string.
The full story
I’ve covered the most common uses for rvalue references, but there are
many subtle points about how defaults work and what happens in
unusual cases.
A good reference for further information is Move semantics and rvalue references in C++11 by Alex Allain.
New Features of C++ 11
C++ 11 extensions There are many other significant new features in C++ 11 besides move semantics. Here are some of the more important ones:
See The Biggest Changes in C++11 (and Why You Should Care) by Danny Kalev for a discussion and examples.