434 lines
9.7 KiB
Markdown
434 lines
9.7 KiB
Markdown
<!-- .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<T></code> calls <code>delete</code>
|
||
* <!-- .element: class="fragment fade-in" --> <code>std::unique_ptr<T[]></code> calls <code>delete[]</code>
|
||
* <!-- .element: class="fragment fade-in" --> <code>std::unique_ptr<T[]></code> has additional <code>operator[]</code> for accessing array element
|
||
* <!-- .element: class="fragment fade-in" --> Usually <code>std::vector<T></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>
|