# Best practices
___
## Best practices
* Rule of 0, Rule of 5
* Avoid explicit new
* Use std::make_shared() / std::make_unique()
* Avoid copying std::shared_ptr<> when this is not neccessary.
* Use references instead of pointers (as argument to function)
* Almost always use unique_ptr
* Use shared_ptr ONLY when you need to share ownership of object.
___
## Rule of 0, Rule of 5
### Rule of 5
* If you need to implement one of those functions:
* destructor
* copy constructor
* copy assignment operator
* move constructor
* move assignment operator
* It probably means that you should implement them all, because you have manual resources management.
### Rule of 0
* If you use RAII wrappers on resources, you don’t need to implement any of Rule of 5 functions.
___
## Avoid explicit `new`
* Smart pointers eliminate the need to use delete explicitly
* To be symmetrical, do not use new as well
* Allocate using:
* std::make_unique()
* std::make_shared()
* use new only when you need to create ptr with custom deleter
___
### Use `std::make_shared()` / `std::make_unique()`
* What is a problem here?
```cpp
struct MyData { int value; };
using Ptr = std::shared_ptr;
void sink(Ptr oldData, Ptr newData);
void use(void) {
sink(Ptr{new MyData{41}}, Ptr{new MyData{42}});
}
```
* Hint: this version is not problematic
```cpp
struct MyData { int value; };
using Ptr = std::shared_ptr;
void sink(Ptr oldData, Ptr newData);
void use(void) {
Ptr oldData{new MyData{41}};
Ptr newData{new MyData{42}};
sink(std::move(oldData), std::move(newData));
}
```
___
### Allocation deconstructed
`auto p = new MyData(10);` means:
* allocate sizeof(MyData) bytes
* run MyData constructor
* assign address of allocated memory to p
Order of evaluation of any part of any expression, including order of evaluation of function arguments is **unspecified**. The compiler can evaluate operands and other subexpressions in any order, and may choose another order when the same expression is evaluated again. There is no concept of left-to-right or right-to-left evaluation in C++. This is not a problem since C++17 due to the changes in the evaluation order of function arguments. Specifically, each argument to a function is required to fully execute before evaluation of other arguments.
___
### Unspecified order of evaluation
* How about two such operations (before C++17)?
| first operation (A) | second operation (B) |
| :-------------------------------------------- | :-------------------------------------------- |
| (1) allocate `sizeof(MyData)` bytes | (1) allocate `sizeof(MyData)` bytes |
| (2) run `MyData` constructor | (2) run `MyData` constructor |
| (3) assign address of allocated memory to `p` | (3) assign address of allocated memory to `p` |
* Unspecified order of evaluation means that order can be for example:
* A1, A2, B1, B2, A3, B3
* What if B2 throws an exception?
___
### Use `std::make_shared()` / `std::make_unique()`
* std::make_shared() / std::make_unique() resolves this problem
```cpp
struct MyData{ int value; };
using Ptr = std::shared_ptr;
void sink(Ptr oldData, Ptr newData);
void use() {
sink(std::make_shared(41), std::make_shared(42));
}
```
* Fixes previous bug
* Does not repeat a constructed type
* Does not use explicit new
* Optimizes memory usage (only for std::make_shared())
___
## Copying `std::shared_ptr<>`
```cpp
void foo(std::shared_ptr p);
void bar(std::shared_ptr p) {
foo(p);
}
```
* requires counters incrementing / decrementing
* atomics / locks are not free
* will call destructors
##### Can be better?
___
## Copying `std::shared_ptr<>`
```cpp
void foo(const std::shared_ptr & p);
void bar(const std::shared_ptr & p) {
foo(p);
}
```
* as fast as pointer passing
* no extra operations
* not safe in multithreaded applications
___
### Use references instead of pointers
* What is the difference between a pointer and a reference?
* reference cannot be empty
* reference, once assigned cannot point to anything else
* Priorities of usage (if possible):
* (const) T&
* std::unique_ptr<T>
* std::shared_ptr<T>
* T*
___
## Exercise: List
Take a look at `List.cpp` file, where simple (and buggy) single-linked list is implemented.
* `pushFront` method adds a new `Node` at the begining of the list.
* `findByValue` method iterates over the list and returns the first Node with matching `value` or `nullptr`.
1. Compile and run List application
2. Fix memory leaks without introducing smart pointers
3. Fix memory leaks with smart pointers. What kind of pointers needs to be applied and why?
4. Add function to add a node at the end of the list (try to do this with time complexity O(1))
5. Add function to delete node with provided value.