diff --git a/pages/CSE332S/CSE332S_L13.md b/pages/CSE332S/CSE332S_L13.md index 47c6475..07f73de 100644 --- a/pages/CSE332S/CSE332S_L13.md +++ b/pages/CSE332S/CSE332S_L13.md @@ -1,224 +1,309 @@ # 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 -- 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) +4 major memory segments -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 -- 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`) +Code and global use is fixed -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 +Code segment is "read-only" ```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 g_default_value = 1; + +int main (int argc, char **argv) { + Foo *f = new Foo; + + f->setValue(g_default_value); + + delete f; // programmer must explicitly free dynamic memory + + return 0; } -int main(int argc, char * argv[]){ - IntArray arr = {0,1,2}; - IntArray arr2 = arr; - return 0; +void Foo::setValue(int v) { + this->m_value = v; } ``` -There are two ways to "copy" +![Image of memory layout](https://notenextra.trance-0.com/images/CSE332S/CPP_Function_Memory.png) -- 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 +### Memory, Lifetimes, and Scopes -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. -- 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. +- Are scoped to an expression, e.g., `a = b + 3 * c;` -### 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 -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]; - } +#include +using namespace std; +int main (int, char *[]) { + int * i = new int; // any of these can throw bad_alloc + int * j = new int(3); + int * k = new int[*j]; + int * l = new int[*j]; + for (int m = 0; m < *j; ++m) { // fill the array with loop + l[m] = m; } + 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 -- 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. +### A Basic Issue: Multiple Aliasing ```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) { + Foo f; + Foo *p = &f; + Foo &r = f; + delete p; + return 0; } - -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 -IntArray::IntArray(unsigned int u) - : size_(0), values_(nullptr) { - // exception safe semantics - values_ = new int [u]; - size_ = u; +Foo *bad() { + Foo f; + return &f; // return address of local variable, f is destroyed after function returns } -IntArray::~IntArray() { +Foo &alsoBad() { + Foo f; + return f; // return reference to local variable, f is destroyed after function returns +} - // deallocates heap memory - // that values_ points to, - // so it's not leaked: - // with deep copy, object - // owns the memory - delete [] values_; +Foo mediocre() { + Foo f; + return f; // return copy of local variable, f is destroyed after function returns, danger when f is a large object +} - // the size_ and values_ - // member variables are - // themselves destroyed - // after destructor body +Foo * good() { + Foo *f = new Foo; + 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 +} + +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 -- Implicitly called when object is created +Automatic variables -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 - - 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 +What if we returned a copy? -### 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 - - 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 +Dynamically allocated variables -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 -- Avoids “slicing”: ensures destruction starts at the most derived class destructor (not at some higher base class) +Can you spot 2 problems? + +- 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 createAndInit() { + shared_ptr p = + make_shared (); + init(p);// may throw exception + return p; +} + +int run () { + try { + shared_ptr spf = + createAndInit(); + cout << “*spf is ” << *spf; + } catch (...) { + return -1 + } + return 0; +} +``` + +RAII idiom example using shared_ptr + +```cpp +#include +using namespace std; +``` + +- `shared_ptr` assumes and maintains ownership of aliased X +- Can access the aliased X through it (*spf) +- `shared_ptr` 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 createAndInit() { + shared_ptr p = + make_shared (); + init(p);// may throw exception + return p; +} + +int run () { + try { + shared_ptr spf = + createAndInit(); + shared_ptr 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` copy constructor increases count, and its destructor decreases count + +`shared_ptr` destructor calls delete on the pointer to the owned X when count drops to 0 diff --git a/pages/CSE332S/CSE332S_L14.md b/pages/CSE332S/CSE332S_L14.md new file mode 100644 index 0000000..16db469 --- /dev/null +++ b/pages/CSE332S/CSE332S_L14.md @@ -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) diff --git a/public/CSE332S/CPP_Function_Memory.png b/public/CSE332S/CPP_Function_Memory.png new file mode 100644 index 0000000..79e67e1 Binary files /dev/null and b/public/CSE332S/CPP_Function_Memory.png differ