trainings/AdvancedCppV2/Presentation/smart_pointers_unique_ptr.md

434 lines
9.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- .slide: data-background="#ccc" -->
# `std::unique_ptr<>`
___
## `std::unique_ptr<>`
<div>
* <!-- .element: class="fragment fade-in" --> one object == one owner
* <!-- .element: class="fragment fade-in" --> destructor destroys the object
* <!-- .element: class="fragment fade-in" --> copying not allowed
* <!-- .element: class="fragment fade-in" --> moving allowed
* <!-- .element: class="fragment fade-in" --> can use custom deleter
* <!-- .element: class="fragment fade-in" --> <a href="https://quick-bench.com/q/kZs8qerce1or9_0R0QNezh_hVM4" target="_top">0 cost class -> no impact on efficiency</a>
</div>
<img data-src="img/uniqueptrinverted.png" alt="unique pointers" class="plain fragment fade-in">
___
### `std::unique_ptr<>` usage
* Old style approach vs modern approach
<div class="multicolumn">
<div class="col">
```cpp
#include <iostream> // old-style approach
struct Msg {
int getValue() { return 42; }
};
Msg* createMsg() {
return new Msg{};
}
int main() {
auto msg = createMsg();
std::cout << msg->getValue();
delete msg;
}
```
<!-- .element: class="fragment fade-in" -->
</div>
<div class="col">
```cpp
#include <memory> // modern approach
#include <iostream>
struct Msg {
int getValue() { return 42; }
};
std::unique_ptr<Msg> createMsg() {
return std::make_unique<Msg>();
}
int main() {
// unique ownership
auto msg = createMsg();
std::cout << msg->getValue();
}
```
<!-- .element: class="fragment fade-in" -->
</div>
___
### `std::unique_ptr<>` usage
* <!-- .element: class="fragment fade-in" --> Copying is not allowed
* <!-- .element: class="fragment fade-in" --> Moving is allowed
<div class="multicolumn">
<div class="col">
```cpp
std::unique_ptr<MyData> source(void);
void sink(std::unique_ptr<MyData> ptr);
void simpleUsage() {
source();
sink(source());
auto ptr = source();
// sink(ptr); // compilation error
sink(std::move(ptr));
auto p1 = source();
// auto p2 = p1; // compilation error
auto p2 = std::move(p1);
// p1 = p2; // compilation error
p1 = std::move(p2);
}
```
<!-- .element: class="fragment fade-in" -->
</div>
<div class="col">
```cpp
std::unique_ptr<MyData> source(void);
void sink(std::unique_ptr<MyData> ptr);
void collections() {
std::vector<std::unique_ptr<MyData>> v;
v.push_back(source());
auto tmp = source();
// v.push_back(tmp); // compilation error
v.push_back(std::move(tmp));
// sink(v[0]); // compilation error
sink(std::move(v[0]));
}
```
<!-- .element: class="fragment fade-in" -->
</div>
___
#### `std::unique_ptr<>` problem with containers
<div class="col">
<!-- .element: class="fragment fade-in" --> What is wrong with this part of code?
<!-- .element: class="fragment fade-in" -->
```cpp
std::unique_ptr<MyData> source(void);
void sink(std::unique_ptr<MyData> ptr);
void collections() {
std::vector<std::unique_ptr<MyData>> v;
v.push_back(source());
auto tmp = source();
v.push_back(std::move(tmp));
sink(std::move(v[0]));
std::cout << *(v[0]) << '\n';
}
```
</div>
___
#### `std::unique_ptr<>` cooperation with raw pointers
```cpp
#include <memory>
void legacyInterface(int*) {}
void deleteResource(int* p) { delete p; }
void referenceInterface(int&) {}
int main() {
auto ptr = std::make_unique<int>(5);
legacyInterface(ptr.get());
deleteResource(ptr.release());
ptr.reset(new int{10});
referenceInterface(*ptr);
ptr.reset(); // ptr is a nullptr
return 0;
}
```
* <!-- .element: class="fragment fade-in" --> <code>get()</code> returns a raw pointer without releasing the ownership
* <!-- .element: class="fragment fade-in" --> <code>release()</code> returns a raw pointer and release the ownership
* <!-- .element: class="fragment fade-in" --> <code>reset()</code> replaces the manager object
* <!-- .element: class="fragment fade-in" --> <code>operator*()</code> dereferences pointer to the managed object
___
### `std::make_unique()`
```cpp
#include <memory>
struct Msg {
Msg(int i) : value(i) {}
int value;
};
int main() {
auto ptr1 = std::unique_ptr<Msg>(new Msg{5});
auto ptr2 = std::make_unique<Msg>(5); // equivalent to above
return 0;
}
```
`std::make_unique()` is a factory function that produce `unique_ptrs`
<!-- .element: class="fragment fade-in" -->
* <!-- .element: class="fragment fade-in" --> added in C++14 for symmetrical operations on unique and shared pointers
* <!-- .element: class="fragment fade-in" --> avoids bare <code>new</code> expression
___
### `std::unique_ptr<T[]>`
```cpp
struct MyData {};
void processPointer(MyData* md) {}
void processElement(MyData md) {}
using Array = std::unique_ptr<MyData[]>;
void use(void)
{
Array tab{new MyData[42]};
processPointer(tab.get());
processElement(tab[13]);
}
```
* <!-- .element: class="fragment fade-in" --> During destruction
* <!-- .element: class="fragment fade-in" --> <code>std::unique_ptr&ltT&gt</code> calls <code>delete</code>
* <!-- .element: class="fragment fade-in" --> <code>std::unique_ptr&ltT[]&gt</code> calls <code>delete[]</code>
* <!-- .element: class="fragment fade-in" --> <code>std::unique_ptr&ltT[]&gt</code> has additional <code>operator[]</code> for accessing array element
* <!-- .element: class="fragment fade-in" --> Usually <code>std::vector&ltT&gt</code> is a better choice
___
## Exercise: Resource
1. <!-- .element: class="fragment fade-in" --> Compile and run Resource application
2. <!-- .element: class="fragment fade-in" --> Check memory leaks under valgrind
3. <!-- .element: class="fragment fade-in" --> Fix memory leaks with a proper usage of <code>delete</code> operator
4. <!-- .element: class="fragment fade-in" --> Refactor the solution to use <code>std::unique_ptr<></code>
5. <!-- .element: class="fragment fade-in" --> Use <code>std::make_unique()</code>
___
## Exercise: Converter
1. <!-- .element: class="fragment fade-in" --> Compile and run Converter application and check memory leaks under valgrind
2. <!-- .element: class="fragment fade-in" --> Fix code using std::unique_ptr and std::make_unique
3. <!-- .element: class="fragment fade-in" --> Find other issues and fix them (use good practise etc...)
___
## Why virtual D'tor is so important (1)?
```C++
class Resource {
public:
explicit Resource(const std::string& str): str_(str) {}
const std::string& str() const {
return str_;
}
private:
std::string str_;
};
class Converter {
public:
Converter() {
std::cout << "C'tor converter\n";
}
virtual ~Converter() {
std::cout << "D'tor converter\n";
}
virtual void Convert(const std::unique_ptr<Resource>& resource) const = 0;
};
class CurlyBracketConverter : public Converter {
public:
CurlyBracketConverter() {
std::cout << "C'tor CurlyBracketConverter\n";
}
~CurlyBracketConverter() override {
std::cout << "D'tor CurlyBracketConverter\n";
}
void Convert(const std::unique_ptr<Resource>& resource) const override {
std::cout << "{" << resource->str() << "}\n";
}
};
class SquareBracketConverter : public Converter {
public:
SquareBracketConverter() {
std::cout << "C'tor SquareBracketConverter\n";
}
~SquareBracketConverter() override {
std::cout << "D'tor SquareBracketConverter\n";
}
virtual void Convert(const std::unique_ptr<Resource>& resource) const override{
std::cout << "[" << resource->str() << "]\n";
}
};
class Printer {
public:
explicit Printer(std::unique_ptr<Converter> converter): converter_(std::move(converter)) {}
void Print(const std::unique_ptr<Resource>& resource) const {
converter_->Convert(resource);
}
private:
std::unique_ptr<Converter> converter_;
};
int main() {
auto resource = std::make_unique<Resource>("Ala has a cat");
Printer printer(std::make_unique<SquareBracketConverter>());
Printer printer2(std::make_unique<CurlyBracketConverter>());
return 0;
}
```
<!-- .element: style="font-size: 0.8rem" -->
___
## Why virtual D'tor is so important (2)?
```C++
C'tor converter
C'tor SquareBracketConverter
C'tor converter
C'tor CurlyBracketConverter
D'tor converter
D'tor converter
```
<!-- .element: style="font-size: 1.2rem" -->
<!-- .element: class="fragment fade-in" -->
Try to add virtual to your D'tor and check result
<!-- .element: class="fragment fade-in" -->
```C++
C'tor converter
C'tor SquareBracketConverter
C'tor converter
C'tor CurlyBracketConverter
D'tor CurlyBracketConverter
D'tor converter
D'tor SquareBracketConverter
D'tor converter
```
<!-- .element: class="fragment fade-in" -->
___
## Custom Deleter
* <!-- .element: class="fragment fade-in" --> When there is a special way to delete object
* <!-- .element: class="fragment fade-in" --> Type of unique_ptr change!
<div class="multicolumn">
<div class="col">
```C++
class Foo {
public:
Foo() {
std::cout << "Foo C'tor\n";
}
void print() const {
std::cout << "Foo!\n";
}
private:
// For some reason, we allow only this function to delete object
friend void deleteMe(Foo* const foo);
~Foo() {
std::cout << "Foo D'tor\n";
}
};
void deleteMe(Foo* const foo) {
std::cout << "Delete object Foo!\n";
delete foo;
}
int main() {
// Can't use make unique, need to use unique_ptr C'tor
// unique_ptr(pointer __p, const deleter_type& __d) noexcept
std::unique_ptr<Foo, void(*)(Foo* const)> ptr(new Foo, deleteMe);
ptr->print();
return 0;
}
```
<!-- .element: style="font-size: 0.8rem" -->
<!-- .element: class="fragment fade-in" -->
</div>
<div class="col">
<!-- .element: class="fragment fade-in" --> Output
```C++
Foo C'tor
Foo!
Delete object Foo!
Foo D'tor
```
<!-- .element: class="fragment fade-in" -->
</div>