196 lines
No EOL
7.9 KiB
Markdown
196 lines
No EOL
7.9 KiB
Markdown
<!-- .slide: data-background="#ccc" -->
|
||
|
||
# Best practices
|
||
|
||
___
|
||
|
||
## Best practices
|
||
|
||
* <!-- .element: class="fragment fade-in" --> Rule of 0, Rule of 5
|
||
* <!-- .element: class="fragment fade-in" --> Avoid explicit <code>new</code>
|
||
* <!-- .element: class="fragment fade-in" --> Use <code>std::make_shared()</code> / <code>std::make_unique()</code>
|
||
* <!-- .element: class="fragment fade-in" --> Avoid copying <code>std::shared_ptr<></code> when this is not neccessary.
|
||
* <!-- .element: class="fragment fade-in" --> Use references instead of pointers (as argument to function)
|
||
* <!-- .element: class="fragment fade-in" --> Almost always use <code>unique_ptr</code>
|
||
* <!-- .element: class="fragment fade-in" --> Use <code>shared_ptr</code> <b>ONLY</b> when you need to share ownership of object.
|
||
|
||
___
|
||
|
||
## Rule of 0, Rule of 5
|
||
|
||
### Rule of 5 <!-- .element: class="fragment fade-in" -->
|
||
|
||
* <!-- .element: class="fragment fade-in" --> If you need to implement one of those functions:
|
||
* <!-- .element: class="fragment fade-in" --> destructor
|
||
* <!-- .element: class="fragment fade-in" --> copy constructor
|
||
* <!-- .element: class="fragment fade-in" --> copy assignment operator
|
||
* <!-- .element: class="fragment fade-in" --> move constructor
|
||
* <!-- .element: class="fragment fade-in" --> move assignment operator
|
||
* <!-- .element: class="fragment fade-in" --> It probably means that you should implement them all, because you have manual resources management.
|
||
|
||
### Rule of 0 <!-- .element: class="fragment fade-in" -->
|
||
|
||
* <!-- .element: class="fragment fade-in" --> If you use RAII wrappers on resources, you don’t need to implement any of Rule of 5 functions.
|
||
|
||
___
|
||
|
||
## Avoid explicit `new`
|
||
|
||
* <!-- .element: class="fragment fade-in" --> Smart pointers eliminate the need to use <code>delete</code> explicitly
|
||
* <!-- .element: class="fragment fade-in" --> To be symmetrical, do not use <code>new</code> as well
|
||
* <!-- .element: class="fragment fade-in" --> Allocate using:
|
||
* <!-- .element: class="fragment fade-in" --> <code>std::make_unique()</code>
|
||
* <!-- .element: class="fragment fade-in" --> <code>std::make_shared()</code>
|
||
* <!-- .element: class="fragment fade-in" --> use <code>new</code> only when you need to create ptr with custom deleter
|
||
|
||
___
|
||
|
||
<!-- .slide: style="font-size: 0.8em" -->
|
||
|
||
### Use `std::make_shared()` / `std::make_unique()`
|
||
|
||
* <!-- .element: class="fragment fade-in" --> What is a problem here?
|
||
|
||
```cpp
|
||
struct MyData { int value; };
|
||
using Ptr = std::shared_ptr<MyData>;
|
||
void sink(Ptr oldData, Ptr newData);
|
||
|
||
void use(void) {
|
||
sink(Ptr{new MyData{41}}, Ptr{new MyData{42}});
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
* <!-- .element: class="fragment fade-in" --> Hint: this version is not problematic
|
||
|
||
```cpp
|
||
struct MyData { int value; };
|
||
using Ptr = std::shared_ptr<MyData>;
|
||
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));
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
___
|
||
|
||
### Allocation deconstructed
|
||
|
||
`auto p = new MyData(10);` means:
|
||
|
||
* <!-- .element: class="fragment fade-in" --> allocate <code>sizeof(MyData)</code> bytes
|
||
* <!-- .element: class="fragment fade-in" --> run <code>MyData</code> constructor
|
||
* <!-- .element: class="fragment fade-in" --> assign address of allocated memory to <code>p</code>
|
||
|
||
Order of evaluation of any part of any expression, including order of evaluation of function arguments is <a href="https://en.cppreference.com/w/cpp/language/eval_order" target="_top">**unspecified**</a>. 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++. <b>This is not a problem since C++17</b> 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.
|
||
|
||
<!-- .element: class="fragment fade-in box" -->
|
||
|
||
___
|
||
<!-- .slide: style="font-size: 0.77em" -->
|
||
|
||
### 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` |
|
||
|
||
* <!-- .element: class="fragment fade-in" --> Unspecified order of evaluation means that order can be for example:
|
||
* A1, A2, B1, B2, A3, B3
|
||
* <!-- .element: class="fragment fade-in" --> What if B2 throws an exception?
|
||
|
||
___
|
||
|
||
### Use `std::make_shared()` / `std::make_unique()`
|
||
|
||
* <!-- .element: class="fragment fade-in" --> <code>std::make_shared()</code> / <code>std::make_unique()</code> resolves this problem
|
||
|
||
```cpp
|
||
struct MyData{ int value; };
|
||
using Ptr = std::shared_ptr<MyData>;
|
||
void sink(Ptr oldData, Ptr newData);
|
||
|
||
void use() {
|
||
sink(std::make_shared<MyData>(41), std::make_shared<MyData>(42));
|
||
}
|
||
```
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
* <!-- .element: class="fragment fade-in" --> Fixes previous bug
|
||
* <!-- .element: class="fragment fade-in" --> Does not repeat a constructed type
|
||
* <!-- .element: class="fragment fade-in" --> Does not use explicit <code>new</code>
|
||
* <!-- .element: class="fragment fade-in" --> Optimizes memory usage (only for <code>std::make_shared()</code>)
|
||
|
||
___
|
||
|
||
## Copying `std::shared_ptr<>`
|
||
|
||
```cpp
|
||
void foo(std::shared_ptr<MyData> p);
|
||
|
||
void bar(std::shared_ptr<MyData> p) {
|
||
foo(p);
|
||
}
|
||
```
|
||
|
||
* <!-- .element: class="fragment fade-in" --> requires counters incrementing / decrementing
|
||
* <!-- .element: class="fragment fade-in" --> atomics / locks are not free
|
||
* <!-- .element: class="fragment fade-in" --> will call destructors
|
||
|
||
##### Can be better?
|
||
<!-- .element: class="fragment fade-in" -->
|
||
|
||
___
|
||
|
||
## Copying `std::shared_ptr<>`
|
||
|
||
```cpp
|
||
void foo(const std::shared_ptr<MyData> & p);
|
||
|
||
void bar(const std::shared_ptr<MyData> & p) {
|
||
foo(p);
|
||
}
|
||
```
|
||
|
||
* <!-- .element: class="fragment fade-in" --> as fast as pointer passing
|
||
* <!-- .element: class="fragment fade-in" --> no extra operations
|
||
* <!-- .element: class="fragment fade-in" --> not safe in multithreaded applications
|
||
|
||
___
|
||
|
||
### Use references instead of pointers
|
||
|
||
* <!-- .element: class="fragment fade-in" --> What is the difference between a pointer and a reference?
|
||
* <!-- .element: class="fragment fade-in" --> reference cannot be empty
|
||
* <!-- .element: class="fragment fade-in" --> reference, once assigned cannot point to anything else
|
||
* <!-- .element: class="fragment fade-in" --> Priorities of usage (if possible):
|
||
* <!-- .element: class="fragment fade-in" --> <code>(const) T&</code>
|
||
* <!-- .element: class="fragment fade-in" --> <code>std::unique_ptr<T></code>
|
||
* <!-- .element: class="fragment fade-in" --> <code>std::shared_ptr<T></code>
|
||
* <!-- .element: class="fragment fade-in" --> <code>T*</code>
|
||
|
||
___
|
||
|
||
## 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. <!-- .element: class="fragment fade-in" --> Compile and run List application
|
||
2. <!-- .element: class="fragment fade-in" --> Fix memory leaks without introducing smart pointers
|
||
3. <!-- .element: class="fragment fade-in" --> Fix memory leaks with smart pointers. What kind of pointers needs to be applied and why?
|
||
4. <!-- .element: class="fragment fade-in" --> Add function to add a node at the end of the list (try to do this with time complexity O(1))
|
||
5. <!-- .element: class="fragment fade-in" --> Add function to delete node with provided value.
|
||
|
||
<!-- .slide: style="font-size: 0.8em" --> |