# 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*