mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-12-19 10:54:06 -06:00
more notes
This commit is contained in:
parent
3b13033bd4
commit
99840099d7
@ -1,3 +0,0 @@
|
|||||||
#cpp #objectOrientedProgramming #polymorphism
|
|
||||||
|
|
||||||
These refer to specifiers and concepts that control polymorphism.
|
|
||||||
1
content/Roboting/C++/C Preproccessor Directives.md
Normal file
1
content/Roboting/C++/C Preproccessor Directives.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
Macros :3
|
||||||
250
content/Roboting/C++/Knobs - Construction and Lifetime.md
Normal file
250
content/Roboting/C++/Knobs - Construction and Lifetime.md
Normal file
@ -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<T>` sole owner (move-only)
|
||||||
|
- `std::shared_ptr<T>` shared owner (has a reference count)
|
||||||
|
- `std::weak_ptr<T>` 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 <memory> <-- for smart pointers
|
||||||
|
|
||||||
|
class Engine {
|
||||||
|
public:
|
||||||
|
void start_engine() const {
|
||||||
|
std::cout << "vroom\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Car {
|
||||||
|
public:
|
||||||
|
explicit Car(std::unique_ptr<Engine> engine)
|
||||||
|
: engine_(std::move(engine) {}
|
||||||
|
|
||||||
|
void start_car() const {
|
||||||
|
engine_->start_engine();
|
||||||
|
std::cout << "driving\n";
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::unique_ptr<Engine> engine_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Factory to create car
|
||||||
|
// std::make_unique<T>(T args...)
|
||||||
|
std::unique_ptr<Car> make_car() {
|
||||||
|
return std::make_unique<Car>(std::make_unique<Engine>())
|
||||||
|
}
|
||||||
|
|
||||||
|
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<std::unique_ptr<Car>> 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<T>` 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<Task> {
|
||||||
|
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<Task>();
|
||||||
|
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<T>` can mutate T if given lock
|
||||||
|
- `std::weak_ptr<const T>` can only read T if given the lock
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// Singly Linked List
|
||||||
|
class Node : public std::enable_shared_from_this<Node> {
|
||||||
|
public:
|
||||||
|
explicit Node(std::string name)
|
||||||
|
: name_(std::move(name)) {}
|
||||||
|
|
||||||
|
void set_child(const std::shared_ptr<Node> 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<Node> child_;
|
||||||
|
std::weak_ptr<Node> parent_;
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
auto root = std::make_shared<Node>("root");
|
||||||
|
auto leaf = std::make_shared<Node>("leaf");
|
||||||
|
|
||||||
|
root->set_child(leaf);
|
||||||
|
leaf->print_chain_up(); // will print leaf <- root <- [root]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
73
content/Roboting/C++/Knobs - Polymorphism.md
Normal file
73
content/Roboting/C++/Knobs - Polymorphism.md
Normal file
@ -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 Derived>
|
||||||
|
class Base {
|
||||||
|
public:
|
||||||
|
void func() { static_cast<Derived*>(this)->func_impl(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class Foo : Base<Foo> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
@ -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<Engine> 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<Engine>(horsepower)) {}
|
||||||
|
void start_car() const {
|
||||||
|
engine_->start();
|
||||||
|
std::cout << "car has started!\n";
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::unique_ptr<Engine> 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<Player*> players;
|
||||||
|
}
|
||||||
|
|
||||||
|
// can also do a similar thing with weak_ptrs
|
||||||
|
class TeamWeakPtr {
|
||||||
|
public:
|
||||||
|
Team() = default;
|
||||||
|
|
||||||
|
void add_player(const std::shared_ptr<Player>& 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<std::weak_ptr<Player>> 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<Player>("alice");
|
||||||
|
auto bob = std::make_shared<Player>("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<std::string, std::string> 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 StoragePolicy>
|
||||||
|
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<MapStorage> 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<std::string, std::string> 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> storage_; // HERE WE ARE POINTING TO THE BASE OBJECT!!
|
||||||
|
public:
|
||||||
|
explicit Repository(std::unique_ptr<Storage> 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<InMemoryStorage>();
|
||||||
|
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<Renderer> create(const std::string& api) {
|
||||||
|
if (api == "opengl") return std::make_unique<OpenGLRenderer>();
|
||||||
|
if (api == "vulkan") return std::make_unique<VulkanRenderer>();
|
||||||
|
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<Renderer> create_renderer() const = 0; // factory method
|
||||||
|
};
|
||||||
|
|
||||||
|
class GameApp : public App {
|
||||||
|
private:
|
||||||
|
std::unique_ptr<Renderer> create_renderer() const override {
|
||||||
|
return std::make_unique<OpenGLRenderer>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class CadApp : public App {
|
||||||
|
private:
|
||||||
|
std::unique_ptr<Renderer> create_renderer() const override {
|
||||||
|
return std::make_unique<VulkanRenderer>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::unique_ptr<App> app = std::make_unique<GameApp>();
|
||||||
|
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<Button> create_button() const = 0;
|
||||||
|
virtual std::unique_ptr<Checkbox> create_checkbox() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DarkFactory : public WidgetFactory {
|
||||||
|
public:
|
||||||
|
std::unique_ptr<Button> create_button() const override { return std::make_unique<DarkButton>(); }
|
||||||
|
std::unique_ptr<Checkbox> create_checkbox() const override { return std::make_unique<DarkCheckbox>(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class LightFactory : public WidgetFactory {
|
||||||
|
public:
|
||||||
|
std::unique_ptr<Button> create_button() const override { return std::make_unique<LightButton>(); }
|
||||||
|
std::unique_ptr<Checkbox> create_checkbox() const override { return std::make_unique<LightCheckbox>(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::unique_ptr<WidgetFactory> f = std::make_unique<DarkFactory>();
|
||||||
|
auto btn = f->create_button();
|
||||||
|
auto cb = f->create_checkbox();
|
||||||
|
btn->paint(); cb->paint();
|
||||||
|
}
|
||||||
|
```
|
||||||
33
content/Roboting/C++/Koenig Lookup.md
Normal file
33
content/Roboting/C++/Koenig Lookup.md
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
Also known as **Argument Dependent Lookup**, and is another one of those "clever but kinda useless" tricks in C++ because, for me, it just makes the code more confusing as we are relying on the compiler to implicitly resolve things.
|
||||||
|
|
||||||
|
When you specify a function with an unqualified name (ie. `swap(a, b)` as opposed to `std::swap(a, b)` or `thing::swap(a, b)`), the compiler will not just search for an existing function in the current scope, but also **within namespaces and classes that are being used inside the current scope.**
|
||||||
|
|
||||||
|
**The main benefit is that it helps reduce namespace pollution.** This can be useful for custom operators.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
namespace demo {
|
||||||
|
class Box {
|
||||||
|
int v_{};
|
||||||
|
public:
|
||||||
|
explicit Box(int v) : v_(v) {}
|
||||||
|
int value() const { return v_; }
|
||||||
|
void swap(Box& other) noexcept { std::swap(v_, other.v_); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Free swap in the same namespace — found by ADL
|
||||||
|
inline void swap(Box& a, Box& b) noexcept { a.swap(b); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic function that wants “the best swap”
|
||||||
|
template <class T>
|
||||||
|
void twiddle(T& a, T& b) {
|
||||||
|
using std::swap; // bring std::swap into consideration
|
||||||
|
swap(a,b); // unqualified → ADL can find demo::swap
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
demo::Box a(1), b(2);
|
||||||
|
twiddle(a, b);
|
||||||
|
std::cout << a.value() << " " << b.value() << "\n"; // 2 1
|
||||||
|
}
|
||||||
|
```
|
||||||
49
content/Roboting/C++/PIMPL.md
Normal file
49
content/Roboting/C++/PIMPL.md
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
Refers to "Pointer to Implementation", and is mostly used to clean up headers.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// ---- Header (would normally live in .hpp) ----
|
||||||
|
class Impl; // forward declaration of implementation
|
||||||
|
|
||||||
|
class Image {
|
||||||
|
public:
|
||||||
|
Image();
|
||||||
|
explicit Image(int w, int h);
|
||||||
|
~Image(); // out-of-line to see full Impl
|
||||||
|
|
||||||
|
void set_pixel(int x, int y, uint8_t v);
|
||||||
|
uint8_t get_pixel(int x, int y) const;
|
||||||
|
void info() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<Impl> p_; // opaque pointer
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- Implementation (would normally live in .cpp) ----
|
||||||
|
class Image::Impl {
|
||||||
|
public:
|
||||||
|
int w{0}, h{0};
|
||||||
|
std::vector<uint8_t> data; // flat grayscale
|
||||||
|
|
||||||
|
Impl() = default;
|
||||||
|
Impl(int W, int H) : w(W), h(H), data(static_cast<size_t>(W*H), 0) {}
|
||||||
|
|
||||||
|
size_t idx(int x, int y) const { return static_cast<size_t>(y*w + x); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// OR IF ITS AN EXTERNAL LIBRARY, YOU JUST #INCLUDE that library in the .cpp
|
||||||
|
|
||||||
|
Image::Image() : p_(std::make_unique<Impl>()) {}
|
||||||
|
Image::Image(int w, int h) : p_(std::make_unique<Impl>(w,h)) {}
|
||||||
|
Image::~Image() = default; // needs full Impl in this TU
|
||||||
|
|
||||||
|
void Image::set_pixel(int x, int y, uint8_t v) {
|
||||||
|
if (x<0 || y<0 || x>=p_->w || y>=p_->h) throw std::out_of_range("pixel");
|
||||||
|
p_->data[p_->idx(x,y)] = v;
|
||||||
|
}
|
||||||
|
uint8_t Image::get_pixel(int x, int y) const {
|
||||||
|
if (x<0 || y<0 || x>=p_->w || y>=p_->h) throw std::out_of_range("pixel");
|
||||||
|
return p_->data[p_->idx(x,y)];
|
||||||
|
}
|
||||||
|
void Image::info() const { std::cout << "Image " << p_->w << "x" << p_->h << "\n"; }
|
||||||
|
|
||||||
|
```
|
||||||
41
content/Roboting/C++/Passing Arguments.md
Normal file
41
content/Roboting/C++/Passing Arguments.md
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#cpp #objectOrientedProgramming
|
||||||
|
## Pass by Value
|
||||||
|
|
||||||
|
Just passes in a copy of the argument into the callee.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
void func(int p) {
|
||||||
|
// neither of these change anything once we leave the scope
|
||||||
|
p = 0;
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pass by Pointer (weird pass by value)
|
||||||
|
|
||||||
|
Passing by pointer is pretty rare in my experience. But what it basically passing a pointer means **you are passing a copy of an address (by value).**
|
||||||
|
|
||||||
|
- The callee can read/write to the pointer if its not const
|
||||||
|
- The callee cannot change the value of the pointer variable (won't change anything outside of scope because pass by value). If you want you could pass the pointer by reference that will do the trick
|
||||||
|
- A raw pointer carries no ownership
|
||||||
|
|
||||||
|
```c++
|
||||||
|
void func(int* p) {
|
||||||
|
if (p) { (*p)++; }// will increment the value of p, change leaves scope
|
||||||
|
p = nullptr; // this doesn't do anything once we leave the scope of this function
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pass by Reference
|
||||||
|
|
||||||
|
**Passes in an alias to the original object into the scope of the callee**. `&` here actually doesn't mean address, its just a language feature that tells you and the compiler that you want the argument to behave like a true alias.
|
||||||
|
|
||||||
|
Under-the-hood, the compiler actually implements the reference as a hidden pointer to make this shit work.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
void func(int & p) {
|
||||||
|
// These will both change p once we are outside the scope
|
||||||
|
p++;
|
||||||
|
p = 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
105
content/Roboting/C++/Std Library Stuff.md
Normal file
105
content/Roboting/C++/Std Library Stuff.md
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
## `std::move`
|
||||||
|
|
||||||
|
**Enables** move constructors and assignments to run. **Does not move anything by itself.**
|
||||||
|
|
||||||
|
```c++
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::string s = "hello";
|
||||||
|
std::string t = std::move(s); // t steals buffer; s is valid but unspecified
|
||||||
|
// s.size() is now 0 or unspecified; don’t rely on contents
|
||||||
|
|
||||||
|
std::vector<std::string> v;
|
||||||
|
std::string temp = "x";
|
||||||
|
v.push_back(std::move(temp)); // moves into vector
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Gotchas**
|
||||||
|
- `std::move(const T&)` produces `const T&&`; most move ctors take `T&&`, so you’ll get a **copy**. Don’t make things `const` if you plan to move.
|
||||||
|
- After moving, objects must be **valid but unspecified**; you can assign/clear/destroy them, but not rely on contents.
|
||||||
|
|
||||||
|
## `std::copy`
|
||||||
|
|
||||||
|
Copies (Deep)
|
||||||
|
|
||||||
|
```c++
|
||||||
|
int main() {
|
||||||
|
std::vector<int> src{1,2,3};
|
||||||
|
std::vector<int> dst;
|
||||||
|
std::copy(src.begin(), src.end(), std::back_inserter(dst)); // append
|
||||||
|
// overlap-safe version:
|
||||||
|
std::vector<int> a{0,1,2,3,4};
|
||||||
|
std::copy_backward(a.begin(), a.begin()+3, a.begin()+4); // shifts left part right
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `std::vector`
|
||||||
|
|
||||||
|
### Construction
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::vector<int> a; // empty
|
||||||
|
std::vector<int> b(5); // size=5, value-initialized (0)
|
||||||
|
std::vector<int> c(5, 42); // size=5, all 42
|
||||||
|
std::vector<int> d{1,2,3}; // initializer-list
|
||||||
|
```
|
||||||
|
|
||||||
|
### Element access (no copies shown)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
v[i]; // unchecked
|
||||||
|
v.at(i); // bounds-checked (throws)
|
||||||
|
v.front(); v.back();
|
||||||
|
v.data(); // contiguous T*
|
||||||
|
```
|
||||||
|
|
||||||
|
### Capacity
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
v.size(); v.capacity(); v.empty();
|
||||||
|
v.reserve(1000); // grow capacity, no size change
|
||||||
|
v.shrink_to_fit(); // non-binding request
|
||||||
|
v.resize(n); // change size (value-init or destroy tail)
|
||||||
|
v.resize(n, value); // grow with value
|
||||||
|
```
|
||||||
|
|
||||||
|
### Modifiers
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
v.push_back(x); // append copy/move
|
||||||
|
v.emplace_back(args...); // construct in-place (avoid temp)
|
||||||
|
v.insert(it, x); // insert before it
|
||||||
|
v.erase(it); // erase element
|
||||||
|
v.clear(); // size -> 0 (capacity unchanged)
|
||||||
|
v.assign(count, value); // replace contents
|
||||||
|
std::swap(v1, v2); // O(1) swap
|
||||||
|
```
|
||||||
|
|
||||||
|
**Erase–remove idiom** (pre C++20):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
v.erase(std::remove(v.begin(), v.end(), value), v.end());
|
||||||
|
```
|
||||||
|
|
||||||
|
### Iteration & algorithms
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
for (auto& x : v) { /* mutate */ }
|
||||||
|
for (const auto& x : v) { /* read */ }
|
||||||
|
|
||||||
|
std::sort(v.begin(), v.end());
|
||||||
|
std::transform(a.begin(), a.end(), b.begin(), [](int x){ return x*x; });
|
||||||
|
```
|
||||||
|
|
||||||
|
### Interop with algorithms
|
||||||
|
|
||||||
|
Use inserters to grow destination:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::vector<int> src{1,2,3}, dst;
|
||||||
|
std::copy(src.begin(), src.end(), std::back_inserter(dst));
|
||||||
|
```
|
||||||
36
content/Roboting/C++/Templates.md
Normal file
36
content/Roboting/C++/Templates.md
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
You can have one implementation work with many types! Note this is a compile-time thing. Templates should also be defined in headers.
|
||||||
|
|
||||||
|
### Function Templates
|
||||||
|
```c++
|
||||||
|
template <class T>
|
||||||
|
T max_of(T a, T b) { return (a < b) ? b : a; }
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
auto x = max_of(3, 7); // T=int
|
||||||
|
auto y = max_of(2.5, 1.0); // T=double
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Class Templates
|
||||||
|
```c++
|
||||||
|
template <class T>
|
||||||
|
class Box {
|
||||||
|
T v_;
|
||||||
|
public:
|
||||||
|
explicit Box(T v) : v_(std::move(v)) {}
|
||||||
|
const T& get() const { return v_; }
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() { Box<std::string> b{"hi"}; }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Non-type template params
|
||||||
|
```c++
|
||||||
|
template <class T, std::size_t N>
|
||||||
|
class StaticArray {
|
||||||
|
T data_[N]{};
|
||||||
|
public:
|
||||||
|
constexpr std::size_t size() const { return N; }
|
||||||
|
T& operator[](std::size_t i){ return data_[i]; }
|
||||||
|
};
|
||||||
|
```
|
||||||
18
content/Roboting/C++/explicit keyword.md
Normal file
18
content/Roboting/C++/explicit keyword.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
This keyword in C++ generally tells the compiler to not do any implicit conversions through that function or constructor.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
struct Meter {
|
||||||
|
explicit Meter(double v) : v_(v) {} // require explicit construction
|
||||||
|
double v_;
|
||||||
|
};
|
||||||
|
|
||||||
|
void use(Meter m) {}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Meter m = 3.0; // ❌ implicit conversion blocked
|
||||||
|
Meter m1(3.0); // ✅
|
||||||
|
Meter m2{3.0}; // ✅
|
||||||
|
// use(3.0); // ❌
|
||||||
|
use(Meter{3.0}); // ✅
|
||||||
|
}
|
||||||
|
```
|
||||||
Loading…
Reference in New Issue
Block a user