update
This commit is contained in:
@@ -1,224 +1,309 @@
|
|||||||
# CSE332S Lecture 13
|
# CSE332S Lecture 13
|
||||||
|
|
||||||
## Copy control
|
## Memory layout of a C++ program, variables and their lifetimes
|
||||||
|
|
||||||
Copy control consists of 5 distinct operations
|
### C++ Memory Overview
|
||||||
|
|
||||||
- A `copy constructor` initializes an object by duplicating the const l-value that was passed to it by reference
|
4 major memory segments
|
||||||
- A `copy-assignment operator` (re)sets an object's 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 (next lecture)
|
|
||||||
- A `move-assignment operator` (re)sets an object's value by transferring the implementation from the r-value reference passed to it (next lecture)
|
|
||||||
|
|
||||||
Today we'll focus on the first 3 operations and will defer the others (introduced in C++11) until next time
|
- Global: variables outside stack, heap
|
||||||
|
- Code (a.k.a. text): the compiled program
|
||||||
|
- Heap: dynamically allocated variables
|
||||||
|
- Stack: parameters, automatic and temporary variables (all the variables that are declared inside a function, managed by the compiler, so must be fixed size)
|
||||||
|
- _For the dynamically allocated variables, they will be allocated in the heap segment, but the pointer (fixed size) to them will be stored in the stack segment._
|
||||||
|
|
||||||
- The others depend on the new C++11 `move semantics`
|
Key differences from Java
|
||||||
|
|
||||||
### Basic copy control operations
|
- Destructors of automatic variables called when stack frame where declared pops
|
||||||
|
- No garbage collection: program must explicitly free dynamic memory
|
||||||
|
|
||||||
A copy constructor or copy-assignment operator takes a reference to a (usually const) instance of the class
|
Heap and stack use varies dynamically
|
||||||
|
|
||||||
- Copy constructor initializes a new object from it
|
Code and global use is fixed
|
||||||
- Copy-assignment operator sets object's 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 `~A()` (except implicit `this`)
|
|
||||||
|
|
||||||
Copy control operations for built-in types
|
Code segment is "read-only"
|
||||||
|
|
||||||
- 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)
|
|
||||||
|
|
||||||
### Preventing or Allowing Basic Copy Control
|
|
||||||
|
|
||||||
Old (C++03) way to prevent compiler from generating a default constructor, copy constructor, destructor, or assignment operator was somewhat awkward
|
|
||||||
|
|
||||||
- Declare private, don't define, don't use within class
|
|
||||||
- This works, but gives cryptic linker error if operation is used
|
|
||||||
|
|
||||||
New (C++11) way to prevent calls to any method
|
|
||||||
|
|
||||||
- End the declaration with `= delete` (and don't define)
|
|
||||||
- Compiler will then give an intelligible error if a call is made
|
|
||||||
|
|
||||||
C++11 allows a constructor to call peer constructors
|
|
||||||
|
|
||||||
- Allows re-use of implementation (through delegation)
|
|
||||||
- Object is fully constructed once any constructor finishes
|
|
||||||
|
|
||||||
C++11 lets you ask compiler to synthesize operations
|
|
||||||
|
|
||||||
- Explicitly, but only for basic copy control, default constructor
|
|
||||||
- End the declaration with `= default` (and don't define) The compiler will then generate the operation or throw an error if it can't.
|
|
||||||
|
|
||||||
## Shallow vs Deep Copy
|
|
||||||
|
|
||||||
### Shallow Copy Construction
|
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// just uses the array that's already in the other object
|
int g_default_value = 1;
|
||||||
IntArray::IntArray(const IntArray &a)
|
|
||||||
:size_(a.size_),
|
int main (int argc, char **argv) {
|
||||||
values_(a.values_) {
|
Foo *f = new Foo;
|
||||||
// only memory address is copied, not the memory it points to
|
|
||||||
|
f->setValue(g_default_value);
|
||||||
|
|
||||||
|
delete f; // programmer must explicitly free dynamic memory
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char * argv[]){
|
void Foo::setValue(int v) {
|
||||||
IntArray arr = {0,1,2};
|
this->m_value = v;
|
||||||
IntArray arr2 = arr;
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
There are two ways to "copy"
|

|
||||||
|
|
||||||
- Shallow: re-aliases existing resources
|
### Memory, Lifetimes, and Scopes
|
||||||
- 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
|
Temporary variables
|
||||||
|
|
||||||
- Efficient but may be risky (why?) The destructor will delete the memory that the other object is pointing to.
|
- Are scoped to an expression, e.g., `a = b + 3 * c;`
|
||||||
- Usually want no-op destructor, aliasing via `shared_ptr` or a boolean value to check if the object is the original memory allocator for the resource.
|
|
||||||
|
|
||||||
### Deep Copy Construction
|
Automatic (stack) variables
|
||||||
|
|
||||||
|
- Are scoped to the duration of the function in which they are declared
|
||||||
|
|
||||||
|
Dynamically allocated variables
|
||||||
|
|
||||||
|
- Are scoped from explicit creation (new) to explicit destruction (delete)
|
||||||
|
|
||||||
|
Global variables
|
||||||
|
|
||||||
|
- Are scoped to the entire lifetime of the program
|
||||||
|
- Includes static class and namespace members
|
||||||
|
- May still have initialization ordering issues
|
||||||
|
|
||||||
|
Member variables
|
||||||
|
|
||||||
|
- Are scoped to the lifetime of the object within which they reside
|
||||||
|
- Depends on whether object is temporary, automatic, dynamic, or global
|
||||||
|
|
||||||
|
**Lifetime of a pointer/reference can differ from the lifetime of the location to which it points/refers**
|
||||||
|
|
||||||
|
## Direct Dynamic Memory Allocation and Deallocation
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
IntArray::IntArray(const IntArray &a)
|
#include <iostream>
|
||||||
:size_(0), values_(nullptr) {
|
using namespace std;
|
||||||
|
int main (int, char *[]) {
|
||||||
if (a.size_ > 0) {
|
int * i = new int; // any of these can throw bad_alloc
|
||||||
// new may throw bad_alloc,
|
int * j = new int(3);
|
||||||
// set size_ after it succeeds
|
int * k = new int[*j];
|
||||||
values_ = new int[a.size_];
|
int * l = new int[*j];
|
||||||
size_ = a.size_;
|
for (int m = 0; m < *j; ++m) { // fill the array with loop
|
||||||
|
l[m] = m;
|
||||||
// could use memcpy instead
|
|
||||||
for (size_t i = 0;
|
|
||||||
i < size_; ++i) {
|
|
||||||
values_[i] = a.values_[i];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
delete i; // call int destructor
|
||||||
|
delete j; // single destructor call
|
||||||
|
delete [] k; // call int destructor for each element
|
||||||
|
delete [] l;
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
int main(int argc, char * argv[]){
|
|
||||||
IntArray arr = {0,1,2};
|
|
||||||
IntArray arr2 = arr;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This code shows deep copy
|
## Issues with direct memory management
|
||||||
|
|
||||||
- Safe: no shared aliasing, exception aware initialization
|
### A Basic Issue: Multiple Aliasing
|
||||||
- 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
|
|
||||||
|
|
||||||
Each object is responsible for its own resources.
|
|
||||||
|
|
||||||
## Swap Trick for Copy-Assignment
|
|
||||||
|
|
||||||
The swap trick is a way to implement the copy-assignment operator, given that the `size_` and `values_` members are already defined in constructor.
|
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
class Array {
|
int main (int argc, char **argv) {
|
||||||
public:
|
Foo f;
|
||||||
Array(unsigned int) ; // assume constructor allocates memory
|
Foo *p = &f;
|
||||||
Array(const Array &); // assume copy constructor makes a deep copy
|
Foo &r = f;
|
||||||
~Array(); // assume destructor calls delete on values_
|
delete p;
|
||||||
Array & operator=(const Array &a);
|
return 0;
|
||||||
private:
|
|
||||||
size_t size_;
|
|
||||||
int * values_;
|
|
||||||
};
|
|
||||||
|
|
||||||
Array & Array::operator=(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 std::swap)
|
|
||||||
}
|
|
||||||
return *this; // previous *values_ cleaned up by temp's destructor, which is the member variable of the current object
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char * argv[]){
|
|
||||||
IntArray arr = {0,1,2};
|
|
||||||
IntArray arr2 = {3,4,5};
|
|
||||||
arr2 = arr;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Review: Construction/destruction order with inheritance, copy control with inheritance
|
Multiple aliases for same object
|
||||||
|
|
||||||
### Constructor and Destructor are Inverses
|
- `f` is a simple alias, the object itself
|
||||||
|
- `p` is a variable holding a pointer
|
||||||
|
- `r` is a variable holding a reference
|
||||||
|
|
||||||
|
What happens when we call delete on p?
|
||||||
|
|
||||||
|
- Destroy a stack variable (may get a bus error there if we’re lucky)
|
||||||
|
- If not, we may crash in destructor of f at function exit
|
||||||
|
- Or worse, a local stack corruption that may lead to problems later
|
||||||
|
|
||||||
|
Problem: object destroyed but another alias to it was then used (**dangling pointer issue**)
|
||||||
|
|
||||||
|
### Memory Lifetime Errors
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
IntArray::IntArray(unsigned int u)
|
Foo *bad() {
|
||||||
: size_(0), values_(nullptr) {
|
Foo f;
|
||||||
// exception safe semantics
|
return &f; // return address of local variable, f is destroyed after function returns
|
||||||
values_ = new int [u];
|
|
||||||
size_ = u;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IntArray::~IntArray() {
|
Foo &alsoBad() {
|
||||||
|
Foo f;
|
||||||
|
return f; // return reference to local variable, f is destroyed after function returns
|
||||||
|
}
|
||||||
|
|
||||||
// deallocates heap memory
|
Foo mediocre() {
|
||||||
// that values_ points to,
|
Foo f;
|
||||||
// so it's not leaked:
|
return f; // return copy of local variable, f is destroyed after function returns, danger when f is a large object
|
||||||
// with deep copy, object
|
}
|
||||||
// owns the memory
|
|
||||||
delete [] values_;
|
|
||||||
|
|
||||||
// the size_ and values_
|
Foo * good() {
|
||||||
// member variables are
|
Foo *f = new Foo;
|
||||||
// themselves destroyed
|
return f; // return pointer to local variable, with new we can return a pointer to a dynamically allocated object, but we must remember to delete it later
|
||||||
// after destructor body
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
Foo *f = &mediocre(); // f is a pointer to a temporary object, which is destroyed after function returns, f is invalid after function returns
|
||||||
|
cout << good()->value() << endl; // good() returns a pointer to a dynamically allocated object, but we did not store the pointer, so it will be lost after function returns, making it impossible to delete it later.
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Constructors initialize
|
|
||||||
|
|
||||||
- At the start of each object's lifetime
|
Automatic variables
|
||||||
- Implicitly called when object is created
|
|
||||||
|
|
||||||
Destructors clean up
|
- Are destroyed on function return
|
||||||
|
- But in bad, we return a pointer to a variable that no longer exists
|
||||||
|
- Reference from also_bad similar
|
||||||
|
- Like an un-initialized pointer
|
||||||
|
|
||||||
- Implicitly called when an object is destroyed
|
What if we returned a copy?
|
||||||
- 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
|
|
||||||
|
|
||||||
### More on Initialization and Destruction
|
- Ok, we avoid the bad pointer, and end up with an actual object
|
||||||
|
- But we do twice the work (why?)
|
||||||
|
- And, it’s a temporary variable (more on this next)
|
||||||
|
|
||||||
Initialization follows a well defined order
|
We really want dynamic allocation here
|
||||||
|
|
||||||
- Base class constructor is called
|
Dynamically allocated variables
|
||||||
- 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
|
- Are not garbage collected
|
||||||
|
- But are lost if no one refers to them: called a "**memory leak**"
|
||||||
|
|
||||||
- Destructor body is run, then member destructors, then base class destructor (which recursively follows reverse order)
|
Temporary variables
|
||||||
|
|
||||||
**Make destructor virtual if members are virtual**
|
- Are destroyed at end of statement
|
||||||
|
- Similar to problems w/ automatics
|
||||||
|
|
||||||
- Or if class is part of an inheritance hierarchy
|
Can you spot 2 problems?
|
||||||
- Avoids “slicing”: ensures destruction starts at the most derived class destructor (not at some higher base class)
|
|
||||||
|
- One with a temporary variable
|
||||||
|
- One with dynamic allocation
|
||||||
|
|
||||||
|
### Double Deletion Errors
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
int main (int argc, char **argv) {
|
||||||
|
Foo *f = new Foo;
|
||||||
|
delete f;
|
||||||
|
// ... do other stuff
|
||||||
|
delete f; // will throw an error because f is already deleted
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
What could be at this location?
|
||||||
|
|
||||||
|
- Another heap variable
|
||||||
|
- Could corrupt heap
|
||||||
|
|
||||||
|
## Shared pointers and the RAII idiom
|
||||||
|
|
||||||
|
### A safer approach using smart pointers
|
||||||
|
|
||||||
|
C++11 provides two key dynamic allocation features
|
||||||
|
|
||||||
|
- `shared_ptr` : a reference counted pointer template to alias and manage objects allocated in dynamic memory (we’ll mostly use the shared_ptr smart pointer in this course)
|
||||||
|
- `make_shared` : a function template that dynamically allocates and value initializes an object and then returns a shared pointer to it (hiding the object’s address, for safety)
|
||||||
|
|
||||||
|
C++11 provides 2 other smart pointers as well
|
||||||
|
|
||||||
|
- `unique_ptr` : a more complex but potentially very efficient way to transfer ownership of dynamic memory safely (implements C++11 “move semantics”)
|
||||||
|
- `weak_ptr` : gives access to a resource that is guarded by a shared_ptr without increasing reference count (can be used to prevent memory leaks due to circular references)
|
||||||
|
|
||||||
|
### Resource Acquisition Is Initialization (RAII)
|
||||||
|
|
||||||
|
Also referred to as the "Guard Idiom"
|
||||||
|
|
||||||
|
- However, the term "RAII" is more widely used for C++
|
||||||
|
|
||||||
|
Relies on the fact that in C++ a stack object’s destructor is called when stack frame pops
|
||||||
|
|
||||||
|
Idea: we can use a stack object (usually a smart pointer) to hold the ownership of a heap object, or any other resource that requires explicit clean up
|
||||||
|
|
||||||
|
- Immediately initialize stack object with the allocated resource
|
||||||
|
- De-allocate resource in the stack object’s destructor
|
||||||
|
|
||||||
|
### Example: Resource Acquisition Is Initialization (RAII)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
shared_ptr<Foo> createAndInit() {
|
||||||
|
shared_ptr<Foo> p =
|
||||||
|
make_shared<Foo> ();
|
||||||
|
init(p);// may throw exception
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
int run () {
|
||||||
|
try {
|
||||||
|
shared_ptr<Foo> spf =
|
||||||
|
createAndInit();
|
||||||
|
cout << “*spf is ” << *spf;
|
||||||
|
} catch (...) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
RAII idiom example using shared_ptr
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <memory>
|
||||||
|
using namespace std;
|
||||||
|
```
|
||||||
|
|
||||||
|
- `shared_ptr<X>` assumes and maintains ownership of aliased X
|
||||||
|
- Can access the aliased X through it (*spf)
|
||||||
|
- `shared_ptr<X>` destructor calls delete on address of owned X when it’s safe to do so (per reference counting idiom discussed next)
|
||||||
|
- Combines well with other memory idioms
|
||||||
|
|
||||||
|
### Reference Counting
|
||||||
|
|
||||||
|
Basic Problem
|
||||||
|
|
||||||
|
- Resource sharing is often more efficient than copying
|
||||||
|
- But it’s hard to tell when all are done using a resource
|
||||||
|
- Must avoid early deletion
|
||||||
|
- Must avoid leaks (non-deletion)
|
||||||
|
|
||||||
|
Solution Approach
|
||||||
|
|
||||||
|
- Share both the resource and a counter for references to it
|
||||||
|
- Each new reference increments the counter
|
||||||
|
- When a reference is done, it decrements the counter
|
||||||
|
- If count drops to zero, also deletes resource and counter
|
||||||
|
- "last one out shuts off the lights"
|
||||||
|
|
||||||
|
### Reference Counting Example
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
shared_ptr<Foo> createAndInit() {
|
||||||
|
shared_ptr<Foo> p =
|
||||||
|
make_shared<Foo> ();
|
||||||
|
init(p);// may throw exception
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
int run () {
|
||||||
|
try {
|
||||||
|
shared_ptr<Foo> spf =
|
||||||
|
createAndInit();
|
||||||
|
shared_ptr<Foo> spf2 = spf;
|
||||||
|
// object destroyed after
|
||||||
|
// both spf and spf2 go away
|
||||||
|
} catch (...) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Again starts with RAII idiom via shared_ptr
|
||||||
|
|
||||||
|
- `spf` initially has sole ownership of aliased X
|
||||||
|
- `spf.unique()` would return true
|
||||||
|
- `spf.use_count` would return 1
|
||||||
|
|
||||||
|
`shared_ptr<X>` copy constructor increases count, and its destructor decreases count
|
||||||
|
|
||||||
|
`shared_ptr<X>` destructor calls delete on the pointer to the owned X when count drops to 0
|
||||||
|
|||||||
224
pages/CSE332S/CSE332S_L14.md
Normal file
224
pages/CSE332S/CSE332S_L14.md
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
# CSE332S Lecture 14
|
||||||
|
|
||||||
|
## Copy control
|
||||||
|
|
||||||
|
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 object's 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 (next lecture)
|
||||||
|
- A `move-assignment operator` (re)sets an object's value by transferring the implementation from the r-value reference passed to it (next lecture)
|
||||||
|
|
||||||
|
Today we'll focus on the first 3 operations and will defer the others (introduced in C++11) until next time
|
||||||
|
|
||||||
|
- The others depend on the new C++11 `move semantics`
|
||||||
|
|
||||||
|
### Basic 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 object's 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 `~A()` (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)
|
||||||
|
|
||||||
|
### Preventing or Allowing Basic Copy Control
|
||||||
|
|
||||||
|
Old (C++03) way to prevent compiler from generating a default constructor, copy constructor, destructor, or assignment operator was somewhat awkward
|
||||||
|
|
||||||
|
- Declare private, don't define, don't use within class
|
||||||
|
- This works, but gives cryptic linker error if operation is used
|
||||||
|
|
||||||
|
New (C++11) way to prevent calls to any method
|
||||||
|
|
||||||
|
- End the declaration with `= delete` (and don't define)
|
||||||
|
- Compiler will then give an intelligible error if a call is made
|
||||||
|
|
||||||
|
C++11 allows a constructor to call peer constructors
|
||||||
|
|
||||||
|
- Allows re-use of implementation (through delegation)
|
||||||
|
- Object is fully constructed once any constructor finishes
|
||||||
|
|
||||||
|
C++11 lets you ask compiler to synthesize operations
|
||||||
|
|
||||||
|
- Explicitly, but only for basic copy control, default constructor
|
||||||
|
- End the declaration with `= default` (and don't define) The compiler will then generate the operation or throw an error if it can't.
|
||||||
|
|
||||||
|
## Shallow vs Deep Copy
|
||||||
|
|
||||||
|
### Shallow Copy Construction
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// just uses the array that's already in the other object
|
||||||
|
IntArray::IntArray(const IntArray &a)
|
||||||
|
:size_(a.size_),
|
||||||
|
values_(a.values_) {
|
||||||
|
// only memory address is copied, not the memory it points to
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char * argv[]){
|
||||||
|
IntArray arr = {0,1,2};
|
||||||
|
IntArray arr2 = arr;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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?) The destructor will delete the memory that the other object is pointing to.
|
||||||
|
- Usually want no-op destructor, aliasing via `shared_ptr` or a boolean value to check if the object is the original memory allocator for the resource.
|
||||||
|
|
||||||
|
### Deep Copy Construction
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
IntArray::IntArray(const IntArray &a)
|
||||||
|
:size_(0), values_(nullptr) {
|
||||||
|
|
||||||
|
if (a.size_ > 0) {
|
||||||
|
// new may throw bad_alloc,
|
||||||
|
// set size_ after it succeeds
|
||||||
|
values_ = new int[a.size_];
|
||||||
|
size_ = a.size_;
|
||||||
|
|
||||||
|
// could use memcpy instead
|
||||||
|
for (size_t i = 0;
|
||||||
|
i < size_; ++i) {
|
||||||
|
values_[i] = a.values_[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int main(int argc, char * argv[]){
|
||||||
|
IntArray arr = {0,1,2};
|
||||||
|
IntArray arr2 = arr;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
Each object is responsible for its own resources.
|
||||||
|
|
||||||
|
## Swap Trick for Copy-Assignment
|
||||||
|
|
||||||
|
The swap trick is a way to implement the copy-assignment operator, given that the `size_` and `values_` members are already defined in constructor.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
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 & Array::operator=(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 std::swap)
|
||||||
|
}
|
||||||
|
return *this; // previous *values_ cleaned up by temp's destructor, which is the member variable of the current object
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char * argv[]){
|
||||||
|
IntArray arr = {0,1,2};
|
||||||
|
IntArray arr2 = {3,4,5};
|
||||||
|
arr2 = arr;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Review: Construction/destruction order with inheritance, copy control with inheritance
|
||||||
|
|
||||||
|
### Constructor and Destructor are Inverses
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
IntArray::IntArray(unsigned int u)
|
||||||
|
: size_(0), values_(nullptr) {
|
||||||
|
// exception safe semantics
|
||||||
|
values_ = new int [u];
|
||||||
|
size_ = u;
|
||||||
|
}
|
||||||
|
|
||||||
|
IntArray::~IntArray() {
|
||||||
|
|
||||||
|
// deallocates heap memory
|
||||||
|
// that values_ points to,
|
||||||
|
// so it's not leaked:
|
||||||
|
// with deep copy, object
|
||||||
|
// owns the memory
|
||||||
|
delete [] values_;
|
||||||
|
|
||||||
|
// the size_ and values_
|
||||||
|
// member variables are
|
||||||
|
// themselves destroyed
|
||||||
|
// after destructor body
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Constructors initialize
|
||||||
|
|
||||||
|
- At the start of each object's 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
|
||||||
|
|
||||||
|
### More 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)
|
||||||
BIN
public/CSE332S/CPP_Function_Memory.png
Normal file
BIN
public/CSE332S/CPP_Function_Memory.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Reference in New Issue
Block a user