diff --git "a/content/Roboting/C++ \"Knobs\"/Polymorphism Knobs.md" "b/content/Roboting/C++ \"Knobs\"/Polymorphism Knobs.md" deleted file mode 100644 index a6e481aa9..000000000 --- "a/content/Roboting/C++ \"Knobs\"/Polymorphism Knobs.md" +++ /dev/null @@ -1,3 +0,0 @@ -#cpp #objectOrientedProgramming #polymorphism - -These refer to specifiers and concepts that control polymorphism. \ No newline at end of file diff --git a/content/Roboting/C++/C Preproccessor Directives.md b/content/Roboting/C++/C Preproccessor Directives.md new file mode 100644 index 000000000..329433776 --- /dev/null +++ b/content/Roboting/C++/C Preproccessor Directives.md @@ -0,0 +1 @@ +Macros :3 \ No newline at end of file diff --git "a/content/Roboting/C++ \"Knobs\"/C++ \"Knobs\".md" "b/content/Roboting/C++/C++ \"Knobs\".md" similarity index 100% rename from "content/Roboting/C++ \"Knobs\"/C++ \"Knobs\".md" rename to "content/Roboting/C++/C++ \"Knobs\".md" diff --git a/content/Roboting/C++/Knobs - Construction and Lifetime.md b/content/Roboting/C++/Knobs - Construction and Lifetime.md new file mode 100644 index 000000000..f7d2eb2c2 --- /dev/null +++ b/content/Roboting/C++/Knobs - Construction and Lifetime.md @@ -0,0 +1,250 @@ +5These are concepts and specifiers that deal with construction, lifetime, and memory management. + +## RAII (Resource Acquisition is Initialization) + +This is a C++ idiom that everyone should follow. It states that the **lifetime of a resource should be tied to the lifetime of the object**. This is to generally stop memory leaks. There are various ways to manage this: +## Rule of 5 + +A more explicit way of managing RAII. + +*If your C++ class manages a resource (not implicitly managed memory, file handle, socket, mutex, etc.), and you need to customize any one of its special member functions, you probably need to define (or explicitly call `= default` / `= delete`) all five:* + +1. Destructor +2. Copy Constructor +3. Copy Assignment +4. Move Constructor +5. Move Assignment + +Reason why is to avoid misusage and memory leaks that can occur from implicit operations. + +```c++ +class Buffer { +public: + // Constructors ( not part of 5, but still needed regardless ) + Buffer() = default; + explicit Buffer(std::size_t n) + : n_(n), data_(n? new int[n]{} : nullptr) {} // creating a raw array int* data_ = new int[n]{} <--- makes an array with 0 initialized as all elements + + // Destructor + ~Buffer() { + delete[] data_; + } + + // Copy Constructor (deep copy) + // we are allowed to access other's private members BECAUSE THEY ARE BOTH OF THE SAME CLASS + Buffer(const Buffer& other) + : n_(other.n_), data_(other.data_ ? new int[other.n_] : nullptr) { + std::copy_n(other.data_, n_, data_); + } + + // Copy Assignment (deep-copy into a temp and then swap) + Buffer& operator=(const Buffer& other) { + if (this != &other) { + Buffer temp(other); + swap(temp); + } + return this*; + } + + // Move Constructor + Buffer(Buffer&& other) noexcept + : n_(other.n_), data_(other.data_) { + other.n_ = 0; + other.data_ = nullptr; // <-- this stops the old data from deleting the new data (double delete) + } + + // Move Assignment + Buffer& operator=(Buffer&& other) noexcept { + if (this != &other) { // <-- self assignment guard, ensures that we are not trying to move assignment the same object in memory + delete[] data_; + n_ = other.n_; + data_ = other.data_; + + // we want to stop the chancs of a double delete + other.n_ = 0; + other.data_ = nullptr; + } + return this*; + } + + // Helper methods + void fill(int v) { + for (std::size_t i = 0; i < n_; i++) { + data_[i] = v; + } + } + + void swap(Buffer& rhs) noexcept { + std::swap(n_, rhs_.n_); + std::swap(data_, rhs_.data_); + } + // Usage of friend here is interesting, its to make it work with std::swap becuase of ADL (Argument Dependant Lookup, Koenig Lookup) + friend void swap(Buffer& a, Buffer& b) noexcept { a.swap(b); } + +private: + std::size_t n_ = 0; + int* data_ = nullptr; +}; + +int main() { + // Use of constructor + Buffer src1(3); src1.fill(11); + Buffer src2(4); src1.fill(22); + Buffer src3(5); src1.fill(33); + Buffer src4(6); src1.fill(44); + + // Use copy constructor (creates a new Buffer with deep-copy) + Buffer copyConstructor = src1; + + // Use of copy assignment (overwrites existing Buffer's content) + Buffer copyAssignment(1); + copyAssignment = src2; + + // Use of move constructor (takes ownership of the contents of src3) + Buffer moveConstructor = std::move(src3); + + // Use of move assignment + Buffer moveAssignment(3); + moveAssignment = std::move(src4); +} + +``` + +For extras in the code block above, see [[Koenig Lookup]] and [[Passing Arguments]] + +## Smart Pointers + +Smart pointers are, in my opinion, a cleaner way to deal with RAII. They are "smarter" than regular pointers because they can help manage memory and ownership. They also reduce the need to deal with the Rule of 5 and managing memory with `new` and `delete`. + +Types of Smart Pointers: +- `std::unique_ptr` sole owner (move-only) +- `std::shared_ptr` shared owner (has a reference count) +- `std::weak_ptr` observer (non-owning handle to a shared_ptr. Loses access once out of scope.) +### Unique Pointer +`unique_ptr` is for objects that should have a single owner at all times. It is move-only, so you should be transferring objects with `std::move`. + +**Great for:** Composition, PIMPL, Containers, Factories + +**Key Points:** +- Moveable but **not copyable** +- Can pass in a **custom deleter** to let the smart pointer know how to properly cleanup resources + - usually is a workaround, you generally won't need to specify a custom deleter if the object you are pointing to has a proper destructor. + - this is usually used with incomplete types and implementations like PIMPL + +```c++ +#include <-- for smart pointers + +class Engine { +public: + void start_engine() const { + std::cout << "vroom\n"; + } +}; + +class Car { +public: + explicit Car(std::unique_ptr engine) + : engine_(std::move(engine) {} + + void start_car() const { + engine_->start_engine(); + std::cout << "driving\n"; + } +private: + std::unique_ptr engine_; +}; + +// Factory to create car +// std::make_unique(T args...) +std::unique_ptr make_car() { + return std::make_unique(std::make_unique()) +} + +int main() { + auto car = make_car(); + car->drive(); + + // This moves the car into the garage + // ie. car is empty and garage[0] now has the car + std::vector> garage; + garage.push_back(std::move(car)); +} +``` + +### Shared Pointer +`shared_ptr` is for objects that could have multple owners. **last shared_ptr that goes out of scope, or is destroyed, actually deletes the object its pointing to (because ref_count goes to 0.** + +**Use when:** parts of your program need to own the same object's lifetime. Good to **avoid by default**... but rclcpp uses this pretty often lol. + +**Key Points:** +- copying increases the ref-count, destruction decreases the ref-count. The last shared_ptr deletes the object it's pointing to. +- `enable_shared_from_this` lets you make a class safely create a `shared_ptr` to itself. This is useful when you wanna run async calls or want to return a shared_ptr to your object for some reason. + +```c++ +// enable_shared_from_this is a curiously recurring template pattern! +class Task : public std::enable_shared_from_this { +public: + void start_async() { + auto self = shared_from_this(); // keep alive during async work + std::thread([self]{ // <-- here! we pass a shared pointer of itself int he thread to keep it alive! + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + std::cout << "Task still alive: " << self.use_count() << " owners\n"; + }).detach(); + } +}; + +int main() { + auto t = std::make_shared(); + t->start_async(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); +} +``` + +#### Why does rclcpp use shared_ptr's so much? +ROS constructs run inside an executor that deals with a bunch of async calls. Thus theres a strong usage of shared_ptr to keep them all alive. The use of ref_count also helps with destruction ordering in the underlying rcl libraries. +### Weak Pointer +`weak_ptr` is a non-owning observer of a `shared_ptr`. It gets permission to read-write to an object owned by a `shared_ptr` within a scope. + +**Use when:** You want to manage a `shared_ptr`-accessed object **without extending its lifetime.** + +**Key Points:** +- `weak_ptr` needs to obtain a lock on the `shared_ptr`'s object before accessing it. +- `std::weak_ptr` can mutate T if given lock +- `std::weak_ptr` can only read T if given the lock + +```c++ +// Singly Linked List +class Node : public std::enable_shared_from_this { +public: + explicit Node(std::string name) + : name_(std::move(name)) {} + + void set_child(const std::shared_ptr child) { + child_ = child; + parent_ = shared_from_this(); + } + + void print_chain_up() const { + std::cout << name_; + if (auto p = parent_.lock()) { // checks if parent is alive + std::cout << " <- "; + p->print_chain_up(); + } else { + std::cout << " <- [root]\n "; + } + } +private: + std::string name_; + std::shared_ptr child_; + std::weak_ptr parent_; +}; + +int main() { + auto root = std::make_shared("root"); + auto leaf = std::make_shared("leaf"); + + root->set_child(leaf); + leaf->print_chain_up(); // will print leaf <- root <- [root] +} +``` + diff --git "a/content/Roboting/C++ \"Knobs\"/Inheritance Knobs.md" b/content/Roboting/C++/Knobs - Inheritance.md similarity index 100% rename from "content/Roboting/C++ \"Knobs\"/Inheritance Knobs.md" rename to content/Roboting/C++/Knobs - Inheritance.md diff --git a/content/Roboting/C++/Knobs - Polymorphism.md b/content/Roboting/C++/Knobs - Polymorphism.md new file mode 100644 index 000000000..c39b76801 --- /dev/null +++ b/content/Roboting/C++/Knobs - Polymorphism.md @@ -0,0 +1,73 @@ +#cpp #objectOrientedProgramming #polymorphism + +These refer to specifiers and concepts that control [[Polymorphism]]. Some common examples are included there. This page contains some more different forms of polymorphism. + +## Curiously Recurring Template Pattern (CRTP) (Static polymorphism) + +This is a C++ idiom where the base class weirdly takes the **derived** class as a template parameter. + +```c++ +template +class Base { +public: + void func() { static_cast(this)->func_impl(); } +}; + +class Foo : Base { +public: + void func_impl() { std::cout << "foo\n"; } +}; +``` + +This lets you do polymorphism without the use of virtual dispatch... It's also pretty weird imo. + +## Functor & Conversion Operators (Static Polymorphism) + +A `functor` is a callable object. That is, it is a class that it itself can act like a function. + +```cpp +class Functor { +public: + double operator()(double D) const { return D * C_; } +private: + double C_; +}; + +int main() { + Functor func(); + double test = 2; + std::cout << func(test); +} +``` + +A conversion operator is an operator that deals with conversions. + +```c++ +struct Meter { + double value{}; + operator double() const { return value; } // implicit, this means it will convert the type to a double when needed, and without you knowing +}; + +int main() { + Meter m{2.5}; + auto a = m + 5; // will work but will implicitly convert all to a double. it will choose a double because that is the only option it has to make this line work (there's only a cast to a double defined) + std::cout << double(m) << std::endl; +} +``` + +If you don't want your code to implicitly convert. Then make it `explicit + +```c++ +struct Meter { + double value{}; + explicit operator double() const { return value; } +}; + +int main() { + Meter m{2.5}; + auto a = m + 5; // this wont work + auto a = double(m) + 5; // this will + std::cout << double(m) << std::endl; +} +``` + diff --git a/content/Roboting/C++/Knobs - Composition, Knobs Aggregation & Dependency Injection.md b/content/Roboting/C++/Knobs - Composition, Knobs Aggregation & Dependency Injection.md new file mode 100644 index 000000000..312de64ba --- /dev/null +++ b/content/Roboting/C++/Knobs - Composition, Knobs Aggregation & Dependency Injection.md @@ -0,0 +1,335 @@ +These are common C++ idioms that people follow when interfacing classes together. + +## Composition + +``` ++-----------+ owns (strong) +| Car |◆───────> +--------+ +| | | Engine | ++-----------+ +--------+ + | + | by-value member or unique_ptr member + v +fields: Engine e_; // or: std::unique_ptr e_; +``` + +The idea here is that the whole owns the part. That is, a derived class contains the object directly as a data member, or as a `unique_ptr`. + +```c++ +class Engine { +public: + explicit Engine(int horsepower) : hp_(horsepower) {} + void start() const noexcept { + std::cout << "vroom\n"; + } +private: + int hp_; +}; + +class Car { +public: + explicit Car(int horsepower) : engine_(horsepower) {} + void start_car() const { + engine_.start(); + std::cout << "car has started!\n"; + } +private: + Engine engine_ +}; + +// You can also do something similar using a unique_ptr +class CarUnique { +public: + explicit Car(int horsepower) : engine_(std::make_unique(horsepower)) {} + void start_car() const { + engine_->start(); + std::cout << "car has started!\n"; + } +private: + std::unique_ptr engine_; +}; +``` +## Aggregation + +``` ++-----------+ non-owning raw ptr +-----------+ +| Team | ------------------------------> | Player | ++-----------+ +-----------+ +``` + +The idea here is that the object has reference to a part it needs, but does not own the part itself. This is to avoid accidental lifetime extensions. + +```c++ +class Player { +public: + Player(std::string name) : name_(name) {} + + void intro() { std::cout << "I am" << name << std::endl; } +private: + std::string name_; +}; + +class Team { +public: + Team() = default; + + void add_player(Player* p) { + players.push_back(p); + } + void roll_call() { + for (auto* p: players_) p->intro(); + } +private: + std::vector players; +} + +// can also do a similar thing with weak_ptrs +class TeamWeakPtr { +public: + Team() = default; + + void add_player(const std::shared_ptr& p) { + players.push_back(p); // this implicitly will store weak_ptrs + } + void roll_call() { + for (const auto& p : players_) { + if (auto p_ptr = p.lock()) { p_ptr->intro(); } + } + } +private: + std::vector> players; +} + +int main() { + Team team(); + TeamWeakPtr team_weak_ptr(); + + // for regular + Player alice("alice"); + Player bob("bob"); + team.add_player(alice); + team.add_player(bob); + team.roll_call(); + + // for weak_ptr + auto alice = std::make_shared("alice"); + auto bob = std::make_shared("bob"); + team.add_player(alice); + team.add_player(bob); + team.roll_call(); +} +``` + +## Dependency Injection + +``` + +-------------------+ + | Storage (IF) |<-- virtual API + +-------------------+ + ^ ^ +implements | | ++-----------------+---+ +---+-----------------+ +| InMemoryStorage | | SqlStorage | ++---------------------+ +---------------------+ + + injected (owns or borrows) ++--------------------+ +| Repository |----->[ Storage, either InMemoryStorage or SqlStorage ] (unique_ptr or reference) ++--------------------+ +``` + +This is the idea of having a class accept a pre-built object. This differs for composition because the object it depends on is initialized in its constructor. +#### Example using Static [[Polymorphism]] + +```c++ +class MapStorage { + std::map kv_; +public: + void put(const std::string& k, std::string v) { kv_[k]=std::move(v); } + std::string get(const std::string& k) const { + auto it = kv_.find(k); return (it==kv_.end()) ? "" : it->second; + } +}; + +template +class RepoT { + StoragePolicy storage_; // value or reference wrapper +public: + RepoT() = default; + explicit RepoT(StoragePolicy s) : storage_(std::move(s)) {} + void save_user(const std::string& id, std::string name) { + storage_.put("user:"+id, std::move(name)); + } + std::string load_user(const std::string& id) const { + return storage_.get("user:"+id); + } +}; + +int main() { + RepoT repo; // choose policy at compile time + repo.save_user("7", "Alice"); + std::cout << repo.load_user("7") << "\n"; +} +``` + +You used this in your templated test classes! To make base test nodes that handle any ros publisher handling any sort of message type! + +#### Example using Dynamic [[Polymorphism]] + +```c++ +class Storage { +public: + virtual ~Storage() = default; + virtual void put(const std::string&, const std::string&) = 0; + virtual std::string get(const std::string&) const = 0; +}; + +// I can implement different storage options and make repository use any of them! +class InMemoryStorage : public Storage { + std::map kv_; +public: + void put(const std::string& k, const std::string& v) override { kv_[k]=v; } + std::string get(const std::string& k) const override { + auto it = kv_.find(k); return (it==kv_.end()) ? "" : it->second; + } +}; + +class Repository { + std::unique_ptr storage_; // HERE WE ARE POINTING TO THE BASE OBJECT!! +public: + explicit Repository(std::unique_ptr s) : storage_(std::move(s)) {} + void save_user(const std::string& id, const std::string& name) { + storage_->put("user:"+id, name); + } + std::string load_user(const std::string& id) const { + return storage_->get("user:"+id); + } +}; + +int main() { + auto mem_storage = std::make_unique(); + Repository repo{std::move(mem_storage)}; // inject impl + repo.save_user("88", "Eddy"); + std::cout << repo.load_user("88") << "\n"; +} +``` + +## Factory + +Generally refers to the idea of having a class or function be responsible for producing the right object with the right internal composition for the end user of the library based on some requirements. + +### Simple Factory + +Most common, accepts parameters and builds an object accordingly. + +```c++ +class Renderer { +public: + virtual ~Renderer() = default; + virtual void draw() const = 0; +}; + +class OpenGLRenderer : public Renderer { +public: + void draw() const override { std::cout << "OpenGL draw\n"; } +}; + +class VulkanRenderer : public Renderer { +public: + void draw() const override { std::cout << "Vulkan draw\n"; } +}; + +class RendererFactory { +public: + static std::unique_ptr create(const std::string& api) { + if (api == "opengl") return std::make_unique(); + if (api == "vulkan") return std::make_unique(); + throw std::invalid_argument("unknown api"); + } +}; + +int main() { + auto r = RendererFactory::create("vulkan"); + r->draw(); +} +``` + +### Factory Method + +No central switch like the simple factory. Instead builds based on defined subclasses using the things you want to use. More of a complete composer of parts. + +```c++ +class Renderer { +public: + virtual ~Renderer() = default; + virtual void draw() const = 0; +}; +class OpenGLRenderer : public Renderer { public: void draw() const override { std::cout << "OpenGL\n"; } }; +class VulkanRenderer : public Renderer { public: void draw() const override { std::cout << "Vulkan\n"; } }; + +class App { +public: + virtual ~App() = default; + void run() const { auto r = create_renderer(); r->draw(); } +private: + virtual std::unique_ptr create_renderer() const = 0; // factory method +}; + +class GameApp : public App { +private: + std::unique_ptr create_renderer() const override { + return std::make_unique(); + } +}; + +class CadApp : public App { +private: + std::unique_ptr create_renderer() const override { + return std::make_unique(); + } +}; + +int main() { + std::unique_ptr app = std::make_unique(); + app->run(); +} +``` +### Abstract Factory + +More of a grouping pattern. Creates families that must match. + +```c++ +class Button { public: virtual ~Button() = default; virtual void paint() const = 0; }; +class Checkbox { public: virtual ~Checkbox() = default; virtual void paint() const = 0; }; + +class DarkButton : public Button { public: void paint() const override { std::cout << "Dark Button\n"; } }; +class DarkCheckbox : public Checkbox { public: void paint() const override { std::cout << "Dark Checkbox\n"; } }; +class LightButton : public Button { public: void paint() const override { std::cout << "Light Button\n"; } }; +class LightCheckbox : public Checkbox { public: void paint() const override { std::cout << "Light Checkbox\n"; } }; + +class WidgetFactory { +public: + virtual ~WidgetFactory() = default; + virtual std::unique_ptr