---
# System prepended metadata

title: 45C Full Review

---

# 45C Full Review 

# Main Overview of Concepts
The "main ideas" of this class revolve around these topics:
- Syntatic differences of C++ to other languages and programming in C++, File Structure, Headers, etc. 
  - The "quirks" of C++ -- Global Declarations compared to Local Ones, Lifecycle of Objects, etc. 
  
- Pointers, References, Dynamic Memory Allocation, Contiguous Memory Allocation and Pointer Arithmetic, Pass-by-value/reference, Memory Manipulation, *Strings*
- Compiler Optimization (through Constant Expressions and Parameters, Copy/Move Construction, etc.)
- Classes and Objects, Definitions of Classes, Inheritance and Polymorphism, Abstract Base Classes and Abstraction 
- ICS46 Prep -- LinkedLists, Dynamic Allocation, `new & delete`, Working with Memory and Data Structures, Defining Data Structures
  - `operator` overloads
- High-Level C++ -- Lambdas, Templates, Namespaces (and Cumulativity of them), Exceptions and Error Handling
  - The Standard Library (STL) -- Iterators, Algorithms, and Standard Containers (`vector`, etc)

- Other important notes documents: [Iterators and Algorithms to Know](/SQmu4fK6SR-E5xnWaHVPzQ), [Data Structures to Know (C++)](/xXs4jZSRRUClGEEEtC5Q5A), [45C Reading Quiz Conceptual Review](/XNSgwrf9QNqn6AlxikCxVA)

# Syntactic Differences and Intrinsic Features 

## Initialization, Construction, etc. 
Every object in C++ has a lifecycle. It:
- Is Born 
  1. Allocated
  2. Constructed `C()`
- Lives
- Dies
  1. Destructed `~C()`
  2. Deallocated
```cpp=
// FOR PRIMITIVES

int x; // declaration, memory allocated, indeterminate value
x = 5; // rvalue copy assignment 

int x { 5 }; // declaration, memory allocated, initialized directly
int x(5) // ^ same here ^
  
int x = 5 // initialization by rvalue copy 
```

- There are 6 main types of constructors in C++, and one destructor
  1. Direct Constructor `C()` 
  2. List Constructor (std::initializer_list) `C{}`
  3. Copy Constructor `C(D())`
  4. Copy Assignment `C() = D()`
  5. Move Constructor `C(std::move(D()))`
  6. Move Assignment  `C() = std::move(D())`
  7. Destructor `~C()`

## Operator Precedence Chart 
- [via cppreference -->](https://en.cppreference.com/w/cpp/language/operator_precedence)
![Screenshot 2024-06-03 at 11.07.18 PM](https://hackmd.io/_uploads/BkQQ1V24C.png)

## LValues and RValues 
Lvalues, known as locator values, are objects that represent an *identifiable* location in memory (i.e. we can get a pointer to them)
- Usually, these are on the LHS of an assignment

RValues are temporary values that do not contain this persistence of memory 
- Usually, appears on the RHS of an assignment, and cannot use the & operator

```cpp=
int x = 5; // 5 is an rvalue, x is now an lvalue 
&5 // --> error 
&x // --> memory addr of x
```

#### Aside: RValue References 
RValue References allow you to bind memory temporarily to rvalues, and are declared with `&&` syntax. They essentially give a temporary object that is about to be destroyed a temporarily accessible location in memory, such that we can access values with it. 
- We can now access them in a deeper scope, and perform different operations on them, such as move assignment and construction for ease of use. 
```cpp=
#include <iostream>

class Resource {
public:
    Resource() { std::cout << "Resource acquired" << std::endl; }
    ~Resource() { std::cout << "Resource destroyed" << std::endl; }
};

void useResource(Resource&& res) {
    std::cout << "Using resource" << std::endl;
}

int main() {
    useResource(Resource()); // Temporary Resource object bound to rvalue reference
    std::cout << "End of main" << std::endl;
    // Resource object is destroyed here
    return 0;
}
```
- Prime example of how `res` can be used even when its destroyed right after the `useResource(Resource())` call

## Templates
Templates are a way to write more dynamic code, with functions that conform to different types.
```cpp=
// here's an example of a print function that prints a type that 
// has a std::ostream& operator<< defined

template 
  <typename T=[default]> // defaults are optional  
void print(const T& arg) { 
  std::cout << arg << std::endl; 
}

// we can now use print with `int`, `double`, `float`, anything!
```
- Entire classes can have templates, functions can have templates, and even members can have templates!

## Lambdas 
Lambdas are anonymous functions that allow for single-use or utility-use or function-object usage.
  - More detailed function-object usage for all types of functions is provided through the `<functional>` header. 
```cpp=
// declaring a lambda:

using return_type = ???; 
using param_type = ???; 

return_type lambda_name = [](param_type p) { 
  return some_function(p); 
}; 

// for example: 
auto is_larger_than_5 = [](const int& x) { 
  return x > 5; 
}; 
```
- In the brackets, you define a *capture* type. 
- `[&var]` -> capture variable by reference 
- `[var]` -> capture variable by copy (value) 
- `[]` -> capture nothing 
- `[&]` -> capture all by reference 
- `[=]` -> capture all by copy (value)


## Namespaces 
![Screenshot 2024-06-04 at 2.24.16 AM](https://hackmd.io/_uploads/HypSTLn40.png)


## Inheritance and Polymorphism, and ABCs
### Inheritance
NOTE: The first parameter in all member functions in C++ is always implicitly `this`, and this bound by the compiler!

In C++, Inheritance works as follows: 
```cpp=
class A {}; 

class B : public/protected/private A {}; 

// now, B is-a A
```

There are 3 types of inheritance: 
1. Public Inheritance: `public` and `protected` members of the base class are `public` and `protected` in the derived class respectively. 
2. Protected Inheritance: `public` and `protected` members of the base are strictly `protected` in the derived
3. Private Inheritance: `public` and `protected` members of the base are strictly `private` in the derived 

- Since C++ supports virtual inheritance, there's the slight issue that arises for MROs similar to ICS 33 Python examples. If you do `[qualifier] virtual Base`, then it makes sure that these chains are avoided and multiple inheritance doesn't cause these overlaps. 
- You can do multiple inheritance with commas easily 

There are 3 ways to qualify members in Classes: 
1. `public`: Visible to all 
2. `protected`: Visible to `this` and `this` type, and all derived
3. `private`: Visible only to `this` and other instances of `this` type

You can only access protected members of the base *directly* in the class or in friend functions

### Polymorphism and ABCs
To make a base class *abstract*, you can tag a class's member with `virtual`. This enables **dynamic binding** of the class and its derived clases.
- `virtual` functions can have default implementations, unless they are tagged with `= 0;`, then they are *purely virtual* and the derived class *must* define them
- If the function isn't tagged with virtual, then the derived class's functions will be passed up in favor of the base class. 
- Destructors can be virtual to make sure all base class resources get cleaned effectively, and also make sure that derived classes that `are-a` base class can still clean their memory up effectively. 
  - Constructors cannot be virtual, as the type of obj. being constructed must be known at compile-time, and the binding is at run-time (hence, *dynamic*)

The order of construction and destruction in (non/multiple) inheritance in C++: 
**Construction**
1. Base Constructor 
2. First Derived Constructor 
3. Nth Derived Constructor 

**Destruction**
1. Nth Derived Destructor 
2. First Derived Destructor 
3. Base Destructor 

*tide-in, tide-out relationship 
- For a more detailed look at polymorphism, think of shapes and areas!

## Exception Handling 
```cpp= 
try {
    // Code that may throw an exception
}
catch (const std::exception& e) {
    // Handle exceptions derived from std::exception
    std::cerr << "Exception: " << e.what() << std::endl;
}
catch (...) {
    // Handle all other exceptions
    std::cerr << "Unknown exception caught" << std::endl;
}


// To raise an error: 
throw int(5); // or 
throw std::string("Hello"); 
// etc..
```


# Pointers 
```cpp=
int x(5); 
int* ptr = &x; // stores a pointer to x 
int& ref = x; // stores a reference to x
// NOTE: the "&" means different things on different sides 
```
Editing a reference is like treating it directly like the object. It will update the same memory, but act like a normal int. Meanwhile, ptr is the memory ITSELF, so editing that is actually changing memory. 
- Doing *ptr *dereferences* the pointer, returning 5 in this case, by *providing* the value directly. 

```cpp=
int x(5); 
int* ptr = &x;
*ptr = 6; 
cout << x << endl; 
// this will output 6 

int x(5); 
int& ref = x; 
ref = 6;
cout << x << endl; 
// this will also output 6
```

## Dynamic Memory 
```cpp=
int* x = new int(5); 
// this dynamically allocated x on the HEAP memory area
```
Note that there are **3** memory areas in C++: 
1. **Global Static**
  - Allocated before main() and deallocated after main()
  - This includes all variables tagged with `static` anywhere in the program, not functions though. 
2. **Stack**
  - Born and Dies within a scope, and predefines memory allocated to the program, defined at compile time and populated randomly at run time. 
  - The stack is *fast*, because the OS knows what to do with that stuff in the program already
3. **Heap**
  - Allocated with `new` or `new[]` and deleted with `delete` or `delete[]`. Heap memory is *always* alive from `new` to `delete`, and if you forget to delete it, it's a memory leak!
  - This allows for run-time memory allocation, but it is at the cost of performance. 
```cpp=
// Dynamic Allocation Example 
#include <iostream> 
using namespace std; 

class Student { 
  std::string name; 
  int year; 
  std::string* classes; 

public: 
  Student(std::string n, int y, std::string* c) : name(n), year(y), classes(c) {}

  void print(ostream& out) {
    out << name << ", Year: " << year << endl;
    for(int i = 0; i < 2; ++i) { 
      // dynamic arrays don't store sizes, i'm just hardcoding for ex.
      out << this->classes[i] << endl;
    }
  }

  ~Student() { 
    delete[] this->classes; // manually clean up memory
    cout << "Here!"; 
  }
};


int main() { 
  std::string* classes = new std::string[2]{
    "ICS 45c", 
    "ICS 6D"
  };

  std::string* classes2 = new std::string[2]{
    "ICS 45c", 
    "ICS 6D"
  };
  
  Student* s = new Student("Test", 1, classes);
  s->print(cout); // now we direct through a ptr
  delete s; // no "Here!" output without this!
  
  Student s2("Test", 2, classes2); 
  s.print(cout) // this is direct, no ptr
    
  delete[] classes; 
  delete[] classes2; // don't forget to clear up dynamic memory!
} 

// THIS WILL OUTPUT THE FOLLOWING: 
Test, Year: 1
ICS 45c
ICS 6D
Here! // heap delete
Test, Year 2
ICS 45c
ICS 6D
Here! // stack dealloc
// Note how the destructor for the stack-allocated "s2"
// was called automatically, but we had to do this manually for 
// heap allocation
```

## Smart Pointers 
Since C++ has so many issues with memory leaks, because humans are inherently fallible and will forget to call `delete[]`, there are a couple utilities added in to make life easier

Defined in `#include <memory>`, `std::shared_ptr<T>` and `std::unique_ptr<T>` 

#### `std::unique_ptr`
Unique pointers *entirely own* the object that they point to, and delete that object as soon as the unique pointer goes out of scope. It also ensures that it is the *only* pointer to that resource. 
- Ownership must be transferred by std::move in the memory header.
```cpp=
#include <iostream>
#include <memory>

int main() {
    std::unique_ptr<int> ptr1 = std::make_unique<int>(10);
    std::cout << *ptr1 << std::endl; // Output: 10

    std::unique_ptr<int> ptr2 = std::move(ptr1); // Transfer ownership to ptr2
    if (ptr1 == nullptr) {
        std::cout << "ptr1 is now nullptr" << std::endl;
    }
    
    // ptr1 goes out of scope, but it does not own the resource anymore
    // ptr2 goes out of scope, and the resource is automatically deleted
}
```

#### `std::shared_ptr`
Shared pointers manage shared ownership of a resource. That way, multiple instances of a shared_ptr can point to the same resource, which is destroyed once the last shared ptr goes out of scope. 
- Reference Counting 
- If needing to make another one, copy a shared ptr, don't `make_shared` to A NEW ONE
```cpp=
#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> ptr1 = std::make_shared<int>(20);
    {
        // std::shared_ptr<int> ptr2 = std::make_shared<int>(20); DO NOT DO THIS!!!!!!!!!
        std::shared_ptr<int> ptr2 = ptr1; // Shared ownership
        std::cout << "ptr2 use_count: " << ptr2.use_count() << std::endl; // Output: 2
    } // ptr2 goes out of scope, but resource is not deleted
    
    std::cout << "ptr1 use_count: " << ptr1.use_count() << std::endl; // Output: 1
    // ptr1 goes out of scope, resource is deleted
}
```

- When using these special constructors for both of these, you can actually just pass in the initialization parameters directly, rather than making a shared ptr to an obj declaration. 


### Other Random Things to Note: 

#### FileIO in C++
```cpp=
ifstream in{ "file.txt" }; // read-in file
ofstream out{ "output.txt" }; // write-out file 
```

C-Strings are defined as `const char*`;

#### Memory Management 
- C++ default is *memberwise* copy/move construction/assignment. If you don't define these behaviors, it just does a shallow copy of everything for you. 
- If you need to manage memory resources, you need to define your own copy/move construction/assignment to make sure that your resources aren't pointing at the same thing and are managed properly. 
- Default is *memberwise*, preferred is *deep*



