Skip to content

Commit cf0f901

Browse files
committed
update move lecture
1 parent 434ab10 commit cf0f901

1 file changed

Lines changed: 100 additions & 35 deletions

File tree

public/lectures/move.md

Lines changed: 100 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,10 @@ int main(int argc, char* argv[]) {
228228
229229
---
230230
231+
# lvalues & rvalues
232+
233+
---
234+
231235
## Disclaimer lvalue a rvalue
232236
233237
* Koncept lvalue a rvalue prešiel počas života C++ mnohými zmenami
@@ -717,6 +721,24 @@ int main() {
717721
* Kód vyššie nejde kompilovať
718722
* Po pridani `const` pred `std::optional` to už skompilovať ide, ale robí sa kópia, keďže typ `std::string` a `std::optional<std::string>` nie sú rovnaké
719723
724+
725+
## Preťaženie funkcie
726+
727+
* V C++ môžeme funkcie preťažiť
728+
729+
```cpp
730+
void func() { }
731+
void func(const std::string& s) { }
732+
733+
int main() {
734+
func();
735+
func("New string");
736+
}
737+
```
738+
739+
* Toto je ale problém ak máme veľa nepovinných parametrov
740+
* Stačí sa pozrieť na štandardnú knižnicu, tam každá funkcia má veľa preťažení
741+
720742
---
721743

722744
## Automatické tvorenie temporary premenných
@@ -804,7 +826,7 @@ int main() {
804826
## rvalue referencie
805827

806828
* Mechanizmus, ktorý nám toto umožňuje sú rvalue referencie
807-
* Umožnujú nám rozlíšiť medzi lvalue a rvalue referenciami a teda vieme, ktoré hodnoty možeme move a ktoré musíme kopírovať
829+
* Umožnujú nám rozlíšiť medzi lvalue a rvalue referenciami a teda vieme, ktoré hodnoty možeme `move`-nuť a ktoré musíme kopírovať
808830
* Funkcie môžu byť preťažené na tieto referencie
809831

810832
---
@@ -915,36 +937,54 @@ private:
915937

916938
## copy
917939

918-
* Kopírovací konštruktor by mal skopírovať objekt
940+
### Kopírovací konštruktor by mal skopírovať objekt
919941

920942
```cpp
921943
buffer(const buffer &other)
922944
: size(other.size)
923-
, ptr(malloc(size)) {
945+
, ptr(malloc(size)) {
946+
if (!ptr) {
947+
// malloc failed
948+
throw std::bad_alloc();
949+
}
950+
// copy buffer
924951
memcpy(ptr, other.ptr, size);
925952
}
926953
```
927954
928955
929-
* Kopírovací operátor priradenia
956+
### Kopírovací operátor priradenia
930957
931958
```cpp
932-
buffer &operator=(const buffer &rhs) {
933-
if (&rhs == this)
959+
buffer& operator=(const buffer& rhs) {
960+
// self-assignment check
961+
if (this == &rhs)
934962
return *this;
935963
964+
void* new_ptr = nullptr;
965+
966+
if (rhs.size > 0) {
967+
new_ptr = malloc(rhs.size);
968+
if (!new_ptr) {
969+
// malloc failed
970+
throw std::bad_alloc();
971+
}
972+
memcpy(new_ptr, rhs.ptr, rhs.size);
973+
}
974+
975+
// Uvoľni staré dáta až po úspešnom alokovaní nových
976+
free(ptr);
977+
978+
ptr = new_ptr;
936979
size = rhs.size;
937-
ptr = malloc(size);
938-
memcpy(ptr, rhs.ptr, size);
980+
939981
return *this;
940982
}
941983
```
942984

943-
* Rátame s tým, že `malloc` sa nemôže pokaziť, bežne by sme to riešili
944-
945985
---
946986

947-
## move
987+
## `move`
948988

949989
* Move konštruktor a operátor priradenia by mali použiť so "starého" objektu najviac ako sa dá
950990

@@ -1067,9 +1107,9 @@ int main() {
10671107
}
10681108
```
10691109

1070-
* Ak sa z funkcie vracia lokálna premenná rovnakého typu ako je návratový typ tak sa urobí automatický move
1071-
* Táto premenná prestáva existvať (je lokálna), takže dáva zmysel ju movnuť
1072-
* Toto je definované v štandarde
1110+
* Ak sa z funkcie vracia lokálna premenná rovnakého typu ako je návratový typ tak sa urobí automatický *move*
1111+
* Táto premenná prestáva existvať (je lokálna), takže dáva zmysel ju *move*-nuť
1112+
* Toto je definované v štandarde a môžeme sa na to spoľahnúť
10731113

10741114
---
10751115

@@ -1107,16 +1147,16 @@ std::string s = std::get<2>(a);
11071147

11081148
```cpp
11091149
auto a = more();
1110-
int i = std::get<int>(a); // fails to compile
1150+
int i = std::get<int>(a); // fails to compile, more than one int
11111151
std::string s = std::get<std::string>(a); // OK
11121152
```
11131153

11141154

11151155
## `std::tie`
11161156

1117-
* Používa sa na automatické naviazanie premenných z ntice
1118-
* Urobí vlastne nticu lvalue referencií
1119-
* Všetko sa movne ak sa dá
1157+
* Používa sa na automatické naviazanie premenných z n-tice
1158+
* Urobí vlastne n-ticu lvalue referencií
1159+
* Všetko sa move-ne ak sa dá
11201160

11211161
```cpp
11221162
std::tuple<int, int, std::string> more() {
@@ -1134,7 +1174,7 @@ int main() {
11341174

11351175
## `std::ignore`
11361176

1137-
* Ak nás niektorá hodnota z ntice nezaujíma, môžeme použiť `std::ignore`
1177+
* Ak nás niektorá hodnota z n-tice nezaujíma, môžeme použiť `std::ignore`
11381178
* Niekedy sa používa aj na potlačenie upozornení, ktoré vyskočia z atribútu `[[nodiscard]]`, ale to nie je úplne špecifikované
11391179

11401180
```cpp
@@ -1239,8 +1279,8 @@ int main() {
12391279
* Temporaries
12401280
* Literals
12411281
* xvalues sú eXpiring values (hodnoty, ktoré nám ďalej už netreba)
1242-
* xvalues sú vytvorené pomocou predtypovania na `&&`, alebo pomocou `std::move, čo je vlastne vnútorne cast
1243-
* Môžeme teda vyvolať move explicitne
1282+
* xvalues sú vytvorené pomocou predtypovania na `&&`, alebo pomocou `std::move`, čo je vlastne vnútorne cast na `T&&`
1283+
* Môžeme teda vyvolať *move* explicitne
12441284

12451285

12461286
## Použitie xvalues
@@ -1262,7 +1302,7 @@ int main() {
12621302
## `std::unique_ptr`
12631303

12641304
* `std::move` sa hodí na použitie s `std::unique_ptr`
1265-
* Pomocou move sa odstránia dangling pointre, ktoré by mohli vzniknúť ak použijeme copy
1305+
* Pomocou *move* sa odstránia dangling pointre, ktoré by mohli vzniknúť ak použijeme copy
12661306

12671307
```cpp
12681308
std::unique_ptr<std::string> ptr;
@@ -1299,8 +1339,8 @@ struct S {
12991339

13001340
## Automatické generovanie
13011341

1302-
* Kompilátor vygeneruje move konštruktor a move operátor priradenia iba ak je to na 100% bezpečné
1303-
* Ak neexistuje `user defined` kopírovací konštruktor
1342+
* Kompilátor vygeneruje *move konštruktor* a *move operátor priradenia* iba ak je to bezpečné
1343+
* Ak neexistuje `user defined` kopírovací konštruktor alebo operátor priradenia
13041344
* Ak neexistuje `user defined` deštruktor
13051345
* Ak chceme vynútiť generovanie použijeme `= default`
13061346

@@ -1317,6 +1357,26 @@ private:
13171357
```
13181358
13191359
1360+
## Ako je to s kopírovacími operáciami?
1361+
1362+
* Ako sme hovorili kompilátor vygeneruje kopírovací konštruktor a operátor priradenia vždy automaticky
1363+
* Existuje ale výnimka
1364+
* Ak je definovaný `user defined` move konštruktor alebo operátor priradenia, tak sa kopírovacie operácie nevygenerujú
1365+
* Taký fix štandardu, kde sa snažili zabrániť auto kopírovaniu, ak je definovaný *move*
1366+
1367+
1368+
## Rule of five
1369+
1370+
* Ak definujeme jednu z týchto piatich operácií, mali by sme definovať všetky
1371+
* Kopírovací konštruktor
1372+
* Kopírovací operátor priradenia
1373+
* Move konštruktor
1374+
* Move operátor priradenia
1375+
* Deštruktor
1376+
* Definícia je aj keď použijeme `= default` alebo `= delete`
1377+
1378+
---
1379+
13201380
## Kanonická implementácia
13211381
13221382
* Nasledujúce implementácie vygeneruje kompilátor
@@ -1384,7 +1444,7 @@ private:
13841444
std::unique_ptr<int> f(std::unique_ptr<int> i) {
13851445
std::unique_ptr<int> ret(new int);
13861446
*ret = *i + 1;
1387-
return ret;
1447+
return ret; // i will be destroyed here
13881448
}
13891449
13901450
int main() {
@@ -1400,9 +1460,9 @@ int main() {
14001460

14011461
* Move je všade v štandardnej knižnici
14021462
* Vector sa snaží urobiť move, keď sa reallokuje
1403-
* `push_back` môže movnuť prvky do vectora
1404-
* Štandardné kontainery (`std::string`, `std::vector`, ...) sa dájú movnuť
1405-
* Veľa funkcionality funguje automaticky a tam kde sa robila kópia sa od C++11 začal robiť move
1463+
* `push_back` môže *move*-nuť prvky do vectora
1464+
* Štandardné kontainery (`std::string`, `std::vector`, ...) sa dajú *move*-nuť
1465+
* Veľa funkcionality funguje automaticky a tam kde sa robila kópia sa od C++11 začal robiť *move*
14061466
* Stačilo prekompilovať novým kompilátorom a mali sme rýchlosť zadarmo
14071467

14081468
---
@@ -1485,7 +1545,7 @@ int main() {
14851545
* Kopírovací a move konštruktor podľa štandardu nemôžu mať side effects
14861546
* Objekt sa vytvorí priamo v stacku volajúcej funkcie
14871547
* Aplikuje sa ak vraciame z funkcie vždy nový objekt
1488-
* Povinné v C++17
1548+
* Povinné v C++17 (pod menom URVO - unnamed RVO)
14891549
14901550
```cpp
14911551
std::string f(int i) {
@@ -1557,24 +1617,29 @@ int main() {
15571617
}
15581618
```
15591619
1560-
* Funkcia môže mať viacero returnov, ale vždy sa musí vracať tá istá premenná
1620+
* Funkcia môže mať viacero `return`-ov, ale vždy sa musí vracať tá istá premenná
15611621
15621622
---
15631623
15641624
## RVO a NRVO manuál
15651625
15661626
* Vždy používajte návratovú hodnotu, nie výstupné parametre
15671627
* Copy, alebo move nesmú mať side effecty
1568-
* Nepoužíva sa v debug builde, to že to nevidíte počas debugovania neznamená, že to nebude v release kóde
1569-
1570-
1571-
## RVO a NRVO manuál
1572-
1628+
* Nepoužíva sa v debug builde, to že to nevidíte počas debugovania neznamená, že to nebude v release kóde (zavisí od kompilátora a verzie štandardu)
15731629
* Preferujme kratšie funkcia (väčšia šanca, že nám to výjde)
15741630
* Ak sa dá vracajme buď vždy nový objekt, alebo vždy tú istú lokálnu premennú
15751631
* Nepreháňajme to, move je už aj tak dostatočne rýchly
15761632
15771633
1634+
## Side effects v copy/move
1635+
1636+
* Side effecty v copy/move sú zakázané
1637+
* Resp. nie zakázané, ale kompilátor môže predpokladať že tam nie sú a optimalizovať podľa toho (RVO/NRVO)
1638+
* Toto je jedno z dvoch miest, kde je povolené vynechať side effects (druhé sú niektoré optimalizácie pri volaní `new`)
1639+
* Najlepšie je robiť triedy tak aby sme dodržali takzvanú "Rule of zero" stratégiu (nepísať vlastné copy/move/deštruktory)
1640+
* Nezabúdať, že niekedy potrebujeme `virtual` deštruktor (toto ale nebýva problém)
1641+
1642+
15781643
## `std::move` môže pokaziť NRVO
15791644
15801645
```cpp

0 commit comments

Comments
 (0)