Title: Copy Control (Part I)
1Copy Control (Part I)
- Copy control consists of 5 distinct operations
- A copy constructor initializes an object by
duplicating the const l-value that was passed to
it by reference - A copy-assignment operator (re)sets an objects
value by duplicating the const l-value passed to
it by reference - A destructor manages the destruction of an object
- A move constructor initializes an object by
transferring the implementation from the r-value
reference passed to it - A move-assignment operator (re)sets an objects
value by transferring the implementation from the
r-value reference passed to it - Today well focus on the first 3 operations and
will defer the others (introduced in C11) until
next time - The others depend on the new C11 move semantics
2Basic Copy Control Operations
- A copy constructor or copy-assignment operator
takes a reference to a (usually const) instance
of the class - Copy constructor initializes a new object from it
- Copy-assignment operator sets objects value from
it - In either case, original the object is left
unchanged (which differs from the move versions
of these operations) - Destructor takes no arguments (except implicit
this) - Copy control operations for built-in types
- Copy construction and copy-assignment copy values
- Destructor of built-in types does nothing (is a
no-op) - Compiler-synthesized copy control operations
- Just call that same operation on each member of
the object - Uses defined/synthesized definition of that
operation for user-defined types (see above for
built-in types)
3Preventing or Allowing Basic Copy Control
- Old (C03) way to prevent compiler from
generating a default constructor, copy
constructor, destructor, or assignment operator
was somewhat awkward - Declare private, dont define, dont use within
class - This works, but gives cryptic linker error if
operation is used - New (C11) way to prevent calls to any method
- End the declaration with delete (and dont
define) - Compiler will then give an intelligible error if
a call is made - C11 allows a constructor to call peer
constructors - Allows re-use of implementation (through
delegation) - Object is fully constructed once any constructor
finishes - C11 lets you ask compiler to synthesize
operations - Explicitly, but only for basic copy control,
default constructor - End the declaration with default (and dont
define)
4Shallow Copy Construction
- // just uses the array thats already in the
other object - IntArrayIntArray(const IntArray a)
- size_(a.size_),
- values_(a.values_)
-
- There are two ways to copy
- Shallow re-aliases existing resources
- E.g., by copying the address value from a pointer
member variable - Deep makes a complete and separate copy
- I.e., by following pointers and deep copying what
they alias - Version above shows shallow copy
- Efficient but may be risky (why?)
- Usually want no-op destructor, aliasing via
shared_ptr
5Deep Copy Construction
- // makes its own copy of the array
- IntArrayIntArray(const IntArray a)
- size_(0), values_(nullptr)
- if (a.size_ gt 0)
- // new may throw bad_alloc,
- // set size_ after it succeeds
- values_ new inta.size_
- size_ a.size_
- // could use memcpy instead
- for (size_t i 0
- i lt size_ i)
- values_i a.values_i
-
-
-
- This code shows deep copy
- Safe no shared aliasing, exception aware
initialization - But may not be as efficient as shallow copy in
many cases - Note trade-offs with arrays
- Allocate memory once
- More efficient than multiple calls to new (heap
search) - Constructor and assignment called on each array
element - Less efficient than block copy
- E.g., using memcpy()
- But sometimes necessary
- i.e., constructors, destructors establish needed
invariants
6Swap Trick for Copy-Assignment
- Cleanup/assignment succeed or fail together
- class Array
- public
- Array(unsigned int) // assume constructor
allocates memory - Array(const Array ) // assume copy
constructor makes a deep copy - Array() // assume destructor calls delete on
values_ - Array operator(const Array a)
- private
- size_t size_
- int values_
-
- Array Arrayoperator(const Array a) //
return ref lets us chain - if (a ! this) // note test for
self-assignment (safe, efficient) - Array temp(a) // copy constructor makes
deep copy of a - swap(temp.size_, size_) // note
unqualified calls to swap - swap(temp.values_, values_) // (do
user-defined or stdswap) -
- return this // previous values_ cleaned up
by temps destructor
7Constructors and Destructors are Inverses
- Constructors initialize
- At the start of each objects lifetime
- implicitly called when object is created
- Destructors clean up
- Implicitly called when an object is destroyed
- E.g., when stack frame where it was declared goes
out of scope - E.g., when its address is passed to delete
- E.g., when another object of which it is a member
is being destroyed
IntArrayIntArray(unsigned int u) size_(0),
values_(nullptr) // exception safe semantics
values_ new int u size_
u IntArrayIntArray() // deallocates
heap memory // that values_ points to, // so
its not leaked // with deep copy, object //
owns the memory delete values_ // the
size_ and values_ // member variables are //
themselves destroyed // after destructor
body
8More on Initialization and Destruction
- Initialization follows a well defined order
- Base class constructor is called
- That constructor recursively follows this order,
too - Member constructors are called
- In order members were declared
- Good style to list in that order (a good compiler
may warn if not) - Constructor body is run
- Destruction occurs in the reverse order
- Destructor body is run, then member destructors,
then base class destructor (which recursively
follows reverse order) - Make destructor virtual if members are virtual
- Or if class is part of an inheritance hierarchy
- Avoids slicing ensures destruction starts at
the most derived class destructor (not at some
higher base class)