{"id":19378,"date":"2023-02-14T05:00:00","date_gmt":"2023-02-14T04:00:00","guid":{"rendered":"https:\/\/sii.pl\/blog\/?p=19378"},"modified":"2023-02-28T16:09:03","modified_gmt":"2023-02-28T15:09:03","slug":"semantyka-przenoszenia-w-c","status":"publish","type":"post","link":"https:\/\/sii.pl\/blog\/semantyka-przenoszenia-w-c\/","title":{"rendered":"Semantyka przenoszenia w C++"},"content":{"rendered":"\n<p>Efektywne zarzadzanie pami\u0119ci\u0105 jest jednym z g\u0142\u00f3wnych problem\u00f3w podczas tworzenia oprogramowania, kt\u00f3re w za\u0142o\u017ceniach ma by\u0107 szybkie i wydajne. Wyr\u00f3\u017cnia si\u0119 tutaj szczeg\u00f3lnie problem kopiowania zasob\u00f3w pomi\u0119dzy obiektami. Cz\u0119sto zdarza si\u0119, \u017ce w takiej sytuacji dodatkowo obiekt \u017ar\u00f3d\u0142owy jest obiektem tymczasowym i bezpo\u015brednio po skopiowaniu zawarto\u015bci jest od razu niszczony.<\/p>\n\n\n\n<p>Ju\u017c od wersji C++98 pojawi\u0142a si\u0119 w standardzie j\u0119zyka optymalizacja znana pod angielsk\u0105 nazw\u0105 \u201ecopy elision\u201d pozwalaj\u0105ca kompilatorom na unikanie kopiowania obiekt\u00f3w w pewnych okre\u015blonych sytuacjach. Przyk\u0142adowo, gdy zawracamy z funkcji obiekt tymczasowy, kompilator mo\u017ce taki obiekt bezpo\u015brednio stworzy\u0107 na stosie w miejscu docelowym. Pomijamy wtedy zupe\u0142nie potrzeb\u0119 wo\u0142ania konstruktora kopiuj\u0105cego. Podsumowuj\u0105c, mamy:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><em>return value optimization (RVO) <\/em>\u2013 opisana powy\u017cej,<\/li><li><em>named return value optimization (NRVO) \u2013 <\/em>gdy nie mamy do czynienia z obiektem tymczasowym,<\/li><li><em>passing by value<\/em> \u2013 gdy przekazujemy do funkcji obiekt tymczasowy przez warto\u015b\u0107,<\/li><li><em>throw by value<\/em> \u2013 gdy wyj\u0105tek jest rzucany przez warto\u015b\u0107.<\/li><\/ul>\n\n\n\n<p>O ile powy\u017csze optymalizacje pozwalaj\u0105 na ca\u0142kowite pomini\u0119cie kopiowania, to jednak obwarowane s\u0105 ca\u0142kiem sporymi ograniczeniami. Przyk\u0142adowo, RVO nie mo\u017ce by\u0107 u\u017cyte w przypadku przypisania do ju\u017c istniej\u0105cego obiektu (assignment operator). Z kolei NRVO nie zadzia\u0142a, je\u015bli zwracany obiekt zale\u017cy od rezultatu dzia\u0142ania samej funkcji. Wi\u0119cej o samym <a aria-label=\" (opens in a new tab)\" href=\"https:\/\/en.cppreference.com\/w\/cpp\/language\/copy_elision\" target=\"_blank\" rel=\"noreferrer noopener\" class=\"ek-link\" rel=\"nofollow\" >copy elision znajdziemy tutaj.<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Semantyka przenoszenia<\/strong><\/h2>\n\n\n\n<p>O semantyce przenoszenia (move semantics) mo\u017cemy my\u015ble\u0107, jak o rozszerzeniu i uzupe\u0142nieniu optymalizacji wspomnianych we wst\u0119pie. Jest to jedna z kluczowych koncepcji wprowadzonych w standardzie j\u0119zyka C++11, kt\u00f3ra pozwala, w pewnych sytuacjach, na przej\u0119cie zasob\u00f3w z obiektu \u017ar\u00f3d\u0142owego podczas operacji przenoszenia. Jest to wa\u017cne z trzech powod\u00f3w:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>przenoszenie w wi\u0119kszo\u015bci przypadk\u00f3w b\u0119dzie szybsze od kopiowania. Nale\u017cy tutaj wspomnie\u0107, i\u017c przenosi\u0107 mo\u017cemy jedynie dane na stercie (dynamicznie zaalokowan\u0105 pami\u0119\u0107, uchwyty do plik\u00f3w) dost\u0119pne najcz\u0119\u015bciej poprzez zmienne sk\u0142adowe wska\u017anikowe,<\/li><li>mo\u017cliwo\u015b\u0107 tworzenia obiekt\u00f3w, kt\u00f3rych nie da si\u0119 skopiowa\u0107, co pozwala na posiadanie zasob\u00f3w na wy\u0142\u0105czno\u015b\u0107 tylko przez jedn\u0105 instancj\u0119 w danej chwili (np. <em>std::unique_ptr, std:: unique_lock<\/em>),<\/li><li>exception safety \u2013 operacja przenoszenia nie wymaga dynamicznej alokacji pami\u0119ci, st\u0105d nie ma ryzyka, \u017ce mo\u017ce nam jej zabrakn\u0105\u0107 (co zwykle ko\u0144czy si\u0119 wyj\u0105tkiem <em>std::bad_alloc<\/em>).<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Referencje do r-warto\u015bci<\/strong><\/h2>\n\n\n\n<p>Semantyka przenoszenia nie by\u0142aby mo\u017cliwa bez rozszerze\u0144 standardu j\u0119zyka C++. Jednym z takich rozszerze\u0144 s\u0105 referencje do r-warto\u015bci. Zapis w kodzie jest podobny do zwyk\u0142ych referencji (l-warto\u015bci), jednak w tym przypadku u\u017cywamy podw\u00f3jnego znaku <em>&amp;&amp;<\/em>:<\/p>\n\n\n\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <em>int &amp;&amp; var = 42;<\/em><\/p>\n\n\n\n<p>Jest to zupe\u0142nie nowy typ referencji (nie jest to referencja do referencji \u2013 taka konstrukcja nie istnieje w C++). Wynik ka\u017cdego wyra\u017cenia r-warto\u015b\u0107 mo\u017cemy od teraz przypisa\u0107 do takiej referencji, co przed\u0142u\u017cy czas \u017cycia tego obiektu (do tej pory wynik takiego wyra\u017cenia mogli\u015bmy co najwy\u017cej przypisa\u0107 do const referencji l-warto\u015b\u0107). Nale\u017cy zwr\u00f3ci\u0107 uwag\u0119 na fakt, i\u017c sama zmienna <em>var<\/em> to ju\u017c l-warto\u015b\u0107, a tylko wyra\u017cenie 42 (literal) to r-warto\u015b\u0107. Podobnie jak w przypadku referencji do l-warto\u015bci, referencje do r-warto\u015bci musz\u0105 zosta\u0107 zainicjalizowane i nie mog\u0105 zmieni\u0107 odniesienia.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Warto\u015bci tymczasowe i wygasaj\u0105ce<\/strong><\/h2>\n\n\n\n<p>Kolejn\u0105 zmian\u0105 jest wprowadzenie nowych typ\u00f3w wyra\u017ce\u0144:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>warto\u015bci w\u0142a\u015bnie stworzone, tymczasowe (prvalues \u2013 pure r-values) \u2013 wszystkie dotychczasowe r-warto\u015bci,<\/li><li>warto\u015bci wygasaj\u0105ce (xrvalues \u2013 expiring value) \u2013 skonwertowane l-warto\u015bci, kt\u00f3re chcemy m\u00f3c przenosi\u0107,<\/li><li>glvalue (\u201cgeneralized\u201d lvalue) \u2013 l-warto\u015bci albo warto\u015bci wygasaj\u0105ce.<\/li><\/ul>\n\n\n\n<p>Z tego powodu zmieni\u0142 si\u0119 spos\u00f3b grupowania na poni\u017cszy:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><img decoding=\"async\" width=\"252\" height=\"191\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/02\/Ryc.-1.png\" alt=\"Sposoby grupowania warto\u015bci w C++\" class=\"wp-image-19381\"\/><figcaption>Ryc. 1 Sposoby grupowania warto\u015bci w C++<\/figcaption><\/figure><\/div>\n\n\n\n<p>Nowy podzia\u0142 wynika z faktu, \u017ce jedynie r-warto\u015bci mog\u0105 by\u0107 przenoszone. Tylko dla takich warto\u015bci mo\u017ce zosta\u0107 u\u017cyta semantyka przenoszenia. Oznacza to, \u017ce je\u015bli chcemy przenie\u015b\u0107 l-warto\u015b\u0107, to najpierw musimy skonwertowa\u0107 j\u0105 do warto\u015bci wygasaj\u0105cej (xvalue). Ma to szczeg\u00f3lnie znaczenie, je\u015bli posiadamy w kodzie prze\u0142adowane funkcje dla l-warto\u015bci i r-warto\u015bci.<\/p>\n\n\n\n<p>Konwersji dokonujemy przy u\u017cyciu szablonowej funkcji <em>std:move<\/em> ze standardowej biblioteki. U\u017cywaj\u0105c <em>std::move<\/em>, mamy pewno\u015b\u0107, \u017ce kompilator wybierze w\u0142a\u015bciwy wariant prze\u0142adowanej funkcji (r-warto\u015b\u0107). Pami\u0119tajmy jednak, \u017ce sama funkcja <em>std::move<\/em> nie wykonuje \u017cadnego przenoszenia, a jedynie rzutowanie na referencje do r-warto\u015bci (<em>static_cast<\/em>).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Funkcje specjalne<\/strong><\/h2>\n\n\n\n<p>Operacja przenoszenia ma sens jedynie w odniesieniu do typ\u00f3w z\u0142o\u017conych (klasy, struktury, unie). W ka\u017cdym innym przypadku wykona si\u0119 zwyk\u0142a operacja kopiowania. Je\u017celi nasze obiekty zarz\u0105dzaj\u0105 zewn\u0119trznymi zasobami, warto wtedy zaimplementowa\u0107 dwie nowe funkcje specjalne: konstruktor przenosz\u0105cy oraz przenosz\u0105cy operator przypisania. Przy czym, je\u015bli w kodzie mamy zaimplementowan\u0105 jedn\u0105 z tych funkcji, najprawdopodobniej powinni\u015bmy doimplementowa\u0107 i drug\u0105 lub (bardziej og\u00f3lnie) powinni\u015bmy doda\u0107 pozosta\u0142e funkcje specjalne zgodnie z <a aria-label=\" (opens in a new tab)\" href=\"https:\/\/en.cppreference.com\/w\/cpp\/language\/rule_of_three#Rule_of_five\" target=\"_blank\" rel=\"noreferrer noopener\" class=\"ek-link\" rel=\"nofollow\" >regu\u0142\u0105 pi\u0119ciu<\/a>.<\/p>\n\n\n\n<p>Nale\u017cy r\u00f3wnie\u017c pami\u0119ta\u0107, \u017ce je\u017celi zdefiniujemy przynajmniej jedn\u0105 z tych funkcji:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>konstruktor kopiuj\u0105cy,<\/li><li>kopiuj\u0105cy operator przypisania,<\/li><li>destruktor,<\/li><li>jedn\u0105 z przenosz\u0105cych funkcji specjalnych,<\/li><\/ul>\n\n\n\n<p>to kompilator nie wygeneruje dla nas nowych funkcji i operacja przenoszenia b\u0119dzie implementowana poprzez kopiowanie elementu (fallback to copy).<\/p>\n\n\n\n<p>Dodatkowo, je\u015bli tylko definiujemy funkcje przenosz\u0105ce, to kompilator nie wygeneruje specjalnych funkcji kopiuj\u0105cych.<\/p>\n\n\n\n<p>Wersje trywialne funkcji przenosz\u0105cych (czyli wygenerowane przez kompilator) wykonaj\u0105 operacje kopiowania (shallow copy) dla typ\u00f3w prostych, podobnie jak ma to miejsce w przypadku trywialnych funkcji kopiuj\u0105cych. W przypadku typ\u00f3w z\u0142o\u017conych zostan\u0105 uruchomione odpowiadaj\u0105ce im funkcje przenosz\u0105ce.<\/p>\n\n\n\n<p>Je\u017celi wiemy, \u017ce wersje trywialne b\u0119d\u0105 dla nas wystarczaj\u0105ce, warto zadba\u0107 o to, aby kompilator zawsze je doda\u0142, u\u017cywaj\u0105c s\u0142owa kluczowego <em>= default<\/em> przy deklaracji.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Konstruktor przenosz\u0105cy<\/strong><\/h2>\n\n\n\n<p>Jednym z przyk\u0142ad\u00f3w u\u017cycia referencji do r-warto\u015bci jest konstruktor przenosz\u0105cy. Jego zadaniem jest przeniesienie zasob\u00f3w obiektu \u017ar\u00f3d\u0142owego. Standard j\u0119zyka C++ nie m\u00f3wi wprost, co si\u0119 powinno sta\u0107 z obiektem przenoszonym. Wa\u017cne jedynie, aby obiekt \u017ar\u00f3d\u0142owy by\u0142 w stanie umo\u017cliwiaj\u0105cym bezpieczne wywo\u0142anie destruktora (np. poprzez ustawienie sk\u0142adowych zmiennych wska\u017anikowych na <em>nullptr<\/em>, aby unikn\u0105\u0107 podw\u00f3jnej dealokacji pami\u0119ci). Konstruktor kopiuj\u0105cy ma nast\u0119puj\u0105c\u0105 posta\u0107 (pe\u0142ny przyk\u0142ad w Za\u0142\u0105czniku 1.):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nbuffer(buffer&amp;&amp; rhs) noexcept\n  : label(std::move(rhs.label)) {\n\tdata = rhs.data;\n\tsize = rhs.size;\n\trhs.data = nullptr;\n\trhs.size = 0;\n}\n<\/pre><\/div>\n\n\n<p>Parametrem jest referencja do r-warto\u015bci obiektu \u017ar\u00f3d\u0142owego, kt\u00f3ry chcemy przenie\u015b\u0107.<\/p>\n\n\n\n<p>Je\u015bli nie ma ryzyka, \u017ce podczas operacji przenoszenia mo\u017ce zosta\u0107 rzucony wyj\u0105tek, oznaczmy nasz konstruktor dodatkowo jako <em>noexcept<\/em>. Pami\u0119tajmy, \u017ce podczas przenoszenia modyfikowany jest obiekt \u017ar\u00f3d\u0142owy, a rzucenie wyj\u0105tku w trakcie tej operacji mo\u017ce spowodowa\u0107 niesp\u00f3jno\u015b\u0107 wewn\u0119trznych zmiennych i w rezultacie utrat\u0119 danych.<\/p>\n\n\n\n<p>Ma to znaczenie, je\u015bli chcemy u\u017cywa\u0107 naszej klasy z kontenerami STL biblioteki standardowej, kt\u00f3re gwarantuj\u0105 sp\u00f3jno\u015b\u0107 danych (strong exception safety guarantee). Je\u015bli konstruktor przenosz\u0105cy nie b\u0119dzie oznaczony jako <em>noexcept<\/em>, standardowe kontenery zawsze b\u0119d\u0105 wykonywa\u0107 operacj\u0119 kopiowania.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Przenosz\u0105cy operator przypisania<\/strong><\/h2>\n\n\n\n<p>Zadaniem przenosz\u0105cego operatora przypisania jest zwolnienie zasob\u00f3w w obiekcie docelowym i pozyskanie zasob\u00f3w z obiektu \u017ar\u00f3d\u0142owego. Podobnie jak poprzednio, powinni\u015bmy pozostawi\u0107 obiekt \u017ar\u00f3d\u0142owy w stanie umo\u017cliwiaj\u0105cym bezpieczne wywo\u0142anie destruktora. Najcz\u0119\u015bciej przenosz\u0105cy operator przypisania przybiera nast\u0119puj\u0105c\u0105 posta\u0107 (pe\u0142ny przyk\u0142ad w Za\u0142\u0105czniku 1.):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nbuffer&amp; operator=(buffer&amp;&amp; rhs) noexcept {\n\tif (this != &amp;rhs) { \/\/ tylko je\u015bli przenosimy z innego obiektu\n\t\tlabel = std::move(rhs.label);\n\t\tdelete &#x5B;] data;\n\t\tdata = rhs.data;\n\t\tsize = rhs.size;\n\t\t\/\/ usuni\u0119cie zasob\u00f3w z obiektu \u017ar\u00f3d\u0142owego pozwoli p\u00f3\u017aniej na bezpieczne zniszczenie obiektu \u017ar\u00f3d\u0142owego\n\t\trhs.data = nullptr;\n\t\trhs.size = 0;\n\t}\n\treturn *this;\n} \n<\/pre><\/div>\n\n\n<p>Parametrem jest referencja do r-warto\u015bci obiektu \u017ar\u00f3d\u0142owego, kt\u00f3ry chcemy przenie\u015b\u0107.<\/p>\n\n\n\n<p>Pami\u0119tajmy, aby oznaczy\u0107 nasz przenosz\u0105cy operator przypisania jako <em>noexcept<\/em> z tego samego powodu, jak przy konstruktorze przenosz\u0105cym.<\/p>\n\n\n\n<p>Ciekaw\u0105 opcj\u0105 jest zastosowanie idiomu copy-and-swap. Zaletami takiego rozwi\u0105zania s\u0105:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>tylko jeden operator przypisania dla operacji kopiowania i przenoszenia (zunifikowany),<\/li><li>nieduplikowanie kodu (Za\u0142\u0105cznik 2.).<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Zwracanie przez warto\u015b\u0107<\/strong><\/h2>\n\n\n\n<p>Semantyka przenoszenia wprowadza te\u017c now\u0105 optymalizacj\u0119: <strong>automatyczne przenoszenie zmiennych lokalnych i parametr\u00f3w<\/strong>. W sytuacji, gdy nie jest mo\u017cliwe unikni\u0119cie kopiowania (copy elision), kompilator spr\u00f3buje tak\u0105 zmienn\u0105 przenie\u015b\u0107. Ze wskazan\u0105 sytuacj\u0105 b\u0119dziemy mieli do czynienia w przedstawionym poni\u017cej przyk\u0142adzie metody wytw\u00f3rczej (fragment pochodzi z Za\u0142\u0105cznika 1.):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nbuffer make_buffer(std::size_t size) {\n    auto buf = buffer(size);\n    return buf;\n}\n<\/pre><\/div>\n\n\n<p>Je\u015bli wynik dzia\u0142ania takiej funkcji przypiszemy bezpo\u015brednio do ju\u017c istniej\u0105cej zmiennej (<em>b1<\/em>), nie b\u0119dzie mo\u017cliwe u\u017cycie optymalizacji NRVO (fragment pochodzi z Za\u0142\u0105cznika 1.):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nb1 = make_buffer(size);\n<\/pre><\/div>\n\n\n<p>Zamiast tego, kompilator spr\u00f3buje przenie\u015b\u0107 zasoby zmiennej buf (wykona si\u0119 przenosz\u0105cy operator przypisania z klasy <em>buffer<\/em>). Czyli pomimo faktu, \u017ce <em>buf<\/em> jest l-warto\u015bci\u0105, to czas \u017cycia takiej zmiennej jest znany i bezpieczne jest potraktowanie jej jako warto\u015b\u0107 wygasaj\u0105ca (xvalue).<\/p>\n\n\n\n<p>B\u0142\u0119dem by\u0142oby natomiast stworzenie funkcji jawnie zwracaj\u0105cej referencje do r-warto\u015bci:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nbuffer&amp;&amp; make_buffer(std::size_t size) {\n    buffer b = buffer(size);\n    return std::move(b);\n}\n<\/pre><\/div>\n\n\n<p>W takiej sytuacji czas \u017cycia zmiennej b nie zostanie wyd\u0142u\u017cony. Ponadto, nie wykona si\u0119 te\u017c NRVO ani operacja przeniesienia. Otrzymamy jedynie referencje do zniszczonego obiektu (dangling reference).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Przekazywanie referencji do r-warto\u015bci<\/strong><\/h2>\n\n\n\n<p>Popatrzmy jeszcze raz na konstruktor przenosz\u0105cy z za\u0142\u0105cznika 1. Zawiera zmienn\u0105 sk\u0142adow\u0105 <em>label<\/em> \u2013 r\u00f3wnie\u017c implementuj\u0105c\u0105 semantyk\u0119 przenoszenia (<em>std::string<\/em>). Czyli w tym przypadku <em>buffer<\/em> nie trzyma bezpo\u015brednio uchwytu do zasobu, bo tzw. \u201ego\u0142y wska\u017anik\u201d jest tutaj opakowany przez klas\u0119 szablonow\u0105 <em>std::string<\/em>.<\/p>\n\n\n\n<p>Bardzo wa\u017cne jest w takiej sytuacji jasne pokazanie kompilatorowi, \u017ce w przypadku zawo\u0142ania konstruktora przenosz\u0105cego, chcemy, aby odpowiednie konstruktory przenosz\u0105ce zosta\u0142y zawo\u0142ane r\u00f3wnie\u017c na zmiennych sk\u0142adowych (pe\u0142ny przyk\u0142ad w Za\u0142\u0105czniku 1.):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nbuffer(buffer&amp;&amp; rhs) noexcept\n  : label(std::move(rhs.label)) { \/\/ wywo\u0142anie konstruktora przenosz\u0105cego std::string\n\tdata = rhs.data;\n\tsize = rhs.size;\n\trhs.data = nullptr;\n\trhs.size = 0;\n}\n<\/pre><\/div>\n\n\n<p>Parametr <em>rhs<\/em> jest l-warto\u015bci\u0105 (pami\u0119tajmy, \u017ce zmienna typu referencja do r-warto\u015bci jest l-warto\u015bci\u0105), wi\u0119c tak\u017ce jego zmienne sk\u0142adowe b\u0119d\u0105 l-warto\u015bciami. Rzutowanie do xvalue przy pomocy <em>std::move<\/em> rozwi\u0105zuje problem. Gdyby\u015bmy tego nie zrobili, kompilator wybra\u0142by konstruktor kopiuj\u0105cy, co w tym przypadku zniweczy\u0142oby ca\u0142y zysk wynikaj\u0105cy z przenoszenia.<\/p>\n\n\n\n<p>Podobnie wygl\u0105da sytuacja w przypadku ka\u017cdej innej nieszablonowej funkcji. Wszystkie parametry funkcji to l-warto\u015bci i musimy wskaza\u0107 (np. poprzez rzutowanie <em>std::move<\/em>), kt\u00f3re prze\u0142adowanie chcemy wywo\u0142a\u0107.<br>Na poni\u017cszym przyk\u0142adzie prze\u0142adowane funkcje <em>pass_buffer<\/em> b\u0119d\u0105 siebie wywo\u0142ywa\u0107 nawzajem, a\u017c do przepe\u0142nienia stosu:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nvoid pass_buffer(const buffer&amp; b) {\n    pass_buffer (std::move(b));\n}\nvoid pass_buffer(buffer&amp;&amp; b) {\n    pass_buffer (b);\n}\n<\/pre><\/div>\n\n\n<p>Oczywi\u015bcie, kolejnym problem b\u0119dzie tu duplikacja kodu, bo zwyk\u0142e funkcje prze\u0142adowane maj\u0105 bardzo podobn\u0105 implementacj\u0119. W takich sytuacjach z pomoc\u0105 przychodz\u0105 funkcje szablonowe oraz uniwersalne referencje (forwarding reference). Zobaczmy przyk\u0142ad z Za\u0142\u0105cznika 1.:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\ntemplate&lt;class... Args&gt;\nbuffer make_buffer_generic(Args&amp;&amp;... args) {\n    return buffer{std::forward&lt;Args&gt;(args)...};\n}\n<\/pre><\/div>\n\n\n<p>Szablonowa funkcja <em>make_buffer_generic<\/em> ma uniwersaln\u0105 implementacj\u0119, niezale\u017cnie od tego, czy argumenty <em>args<\/em> s\u0105 przekazywane jako l-warto\u015b\u0107, czy te\u017c r-warto\u015b\u0107 (a tak\u017ce const i volatile). Powy\u017csza implementacja pozwala kompilatorowi na wybranie poprawnego konstruktora (domy\u015blnego, przenosz\u0105cego lub kopiuj\u0105cego). Tym razem do przekazania parametr\u00f3w u\u017cywamy funkcji <em>std::forward<\/em> ze standardowej biblioteki.<\/p>\n\n\n\n<p>Przy dedukcji typu ma te\u017c zastosowanie nowa regu\u0142a \u2013 zwijanie referencji (reference collapsing). Definiuje ona post\u0119powanie z typami szablonowymi, kt\u00f3re tworzy\u0142yby referencje do referencji. W skr\u00f3cie, typ <em>buffer&amp;&amp; &amp;&amp;<\/em> (rvalue reference to rvalue reference) zostanie uproszczony do typu <em>buffer&amp;&amp;<\/em>. Pozosta\u0142e kombinacje typ\u00f3w zostan\u0105 uproszczone do typu <em>buffer&amp;<\/em>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Wsparcie w bibliotece standardowej<\/strong><\/h2>\n\n\n\n<p>Przyk\u0142adem grupy obiekt\u00f3w implementuj\u0105cych semantyk\u0119 przenoszenia w bibliotece standardowej s\u0105 inteligentne wska\u017aniki (smart pointers). Co ciekawe, pierwsz\u0105 implementacj\u0105 inteligentnego wska\u017anika by\u0142a klasa szablonowa <em>std::auto_ptr<\/em>, kt\u00f3ra pojawi\u0142a si\u0119 w standardowej bibliotece, jeszcze zanim semantyka przenoszenia wesz\u0142a do standardu j\u0119zyka C++.<\/p>\n\n\n\n<p>Przenoszenie obiekt\u00f3w imitowane by\u0142o poprzez funkcje odpowiedzialne za kopiowanie, wi\u0119c \u2013 jak mo\u017cemy si\u0119 domy\u015bla\u0107 \u2013 wykonywa\u0142a swoje zadanie raczej nieudolnie. Wraz z pojawieniem si\u0119 pe\u0142noprawnych mechanizm\u00f3w przenoszenia, klasa ta zosta\u0142a ca\u0142kowicie zast\u0105piona przez <em>std::unique_ptr<\/em>.<\/p>\n\n\n\n<p>Implementacja klasy szablonowej <em>std::unique_ptr<\/em> jest na tyle lekka, \u017ce mo\u017cna ca\u0142kowicie zapomnie\u0107 o przechowywaniu i przekazywaniu tzw. \u201ego\u0142ych wska\u017anik\u00f3w\u201d, a w po\u0142\u0105czeniu z funkcj\u0105 <em>std::make_unique<\/em> mo\u017cemy nawet wyeliminowa\u0107 bezpo\u015brednie u\u017cycie operator\u00f3w <em>new<\/em> i <em>delete<\/em>.<\/p>\n\n\n\n<p>Wsparcie biblioteki standardowej jest oczywi\u015bcie o wiele szersze. Przyk\u0142adowo, funkcje dodaj\u0105ce elementy do kontenera posiadaj\u0105 teraz prze\u0142adowane wersje przenosz\u0105ce (np. <em>std::vector::push_back<\/em>). Do dyspozycji mamy r\u00f3wnie\u017c funkcje pomocnicze <em>std::move<\/em> i <em>std::forward<\/em> bior\u0105ce na siebie ci\u0119\u017car wykonania odpowiedniego rzutowania.<\/p>\n\n\n\n<p>Oddzieln\u0105 grupa s\u0105 obiekty synchronizuj\u0105ce dost\u0119p do zasob\u00f3w (np. <em>std:: unique_lock<\/em>), jak i obiekty reprezentuj\u0105ce zasoby (np. <em>std::thread<\/em>). W obu przypadkach nie chcemy, aby dwa r\u00f3\u017cne obiekty wskazywa\u0142y na ten sam zas\u00f3b, wi\u0119c kopiowanie jest ca\u0142kowicie wy\u0142\u0105czone. Dozwolone jest jedynie przenoszenie zasob\u00f3w pomi\u0119dzy obiektami (np. z u\u017cyciem standardowej funkcji <em>std::swap<\/em>).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Podsumowanie<\/strong><\/h2>\n\n\n\n<p>Pe\u0142ne zrozumienie mechanizm\u00f3w przenoszenia oraz \u015bwiadomo\u015b\u0107 dost\u0119pnych optymalizacji zapobiegaj\u0105cych kopiowaniu (copy elision), jak r\u00f3wnie\u017c wykorzystanie gotowych wzorc\u00f3w z biblioteki standardowej pozwala na wdra\u017canie rozwi\u0105za\u0144 ca\u0142kowicie eliminuj\u0105cych operowanie na typach wska\u017anikowych. Mamy tak\u017ce gwarancj\u0119, \u017ce takie zasoby przenoszone s\u0105 pomi\u0119dzy obiektami w spos\u00f3b bezpieczny oraz zwalniane automatyczne w momencie, gdy niszczony jest obiekt zarz\u0105dzaj\u0105cy.<\/p>\n\n\n\n<p>Semantyka przenoszenia nie nale\u017cy do prostych mechanizm\u00f3w i jej pe\u0142ne zrozumienie wymaga sporo czasu. Wydawa\u0107 by si\u0119 mog\u0142o, \u017ce teraz ka\u017cdy obiekt powinien mie\u0107 w\u0142asne implementacje specjalnych funkcji przenosz\u0105cych. Jednak tworzenie obiekt\u00f3w z w\u0142asn\u0105 implementacj\u0105 konstruktora przenosz\u0105cego oraz przenosz\u0105cego operatora przypisania powinno by\u0107 ostateczno\u015bci\u0105!<\/p>\n\n\n\n<p>W pierwszej kolejno\u015bci powinni\u015bmy si\u0119 stara\u0107 wykorzysta\u0107 gotowe rozwi\u0105zania z biblioteki standardowej (np. <em>std::unique_ptr<\/em>) oraz zadba\u0107, aby trywialne wersje funkcji przenosz\u0105cych by\u0142y tworzone przez kompilator (wymuszenie poprzez = <em>default<\/em>). W naszym prostym przyk\u0142adzie klasy <em>buffer<\/em> \u201eopakowanie\u201d zmiennej sk\u0142adowej data w obiekt smart pointera w zupe\u0142no\u015bci wystarczy\u0142oby, aby zast\u0105pi\u0107 implementacje funkcji specjalnych ich wersjami trywialnymi. <\/p>\n\n\n\n<p>Podsumowuj\u0105c, wprowadzenie semantyki przenoszenia do standardu j\u0119zyka C++ jest du\u017cym krokiem w stron\u0119 urzeczywistnienia sytuacji, w kt\u00f3rej poj\u0119cia takie jak: wycieki pami\u0119ci (memory leaks), podw\u00f3jna dealokacja (double deallocation) czy dangling pointers mog\u0142yby w ko\u0144cu przej\u015b\u0107 do historii \ud83d\ude0a<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Za\u0142\u0105czniki<\/strong><\/h2>\n\n\n\n<div class=\"wp-block-file\"><a id=\"wp-block-file--media-bc3713b8-a721-4d1c-aef5-9b7f551c57a9\" href=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/02\/Zalacznik-1.docx\">Za\u0142\u0105cznik 1.<\/a><a href=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/02\/Zalacznik-1.docx\" class=\"wp-block-file__button\" download aria-describedby=\"wp-block-file--media-bc3713b8-a721-4d1c-aef5-9b7f551c57a9\">Pobierz<\/a><\/div>\n\n\n\n<div class=\"wp-block-file\"><a id=\"wp-block-file--media-7e1d1008-ced2-4e7a-8c57-3973a5c03dd8\" href=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/02\/Zalacznik-2.docx\">Za\u0142\u0105cznik 2.<\/a><a href=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/02\/Zalacznik-2.docx\" class=\"wp-block-file__button\" download aria-describedby=\"wp-block-file--media-7e1d1008-ced2-4e7a-8c57-3973a5c03dd8\">Pobierz<\/a><\/div>\n\n\n\n<p>***<\/p>\n\n\n\n<p>Je\u017celi interesuje Ci\u0119 j\u0119zyk C lub C++, zach\u0119camy do sprawdzenia innych artyku\u0142\u00f3w naszych ekspert\u00f3w m.in.:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"https:\/\/sii.pl\/blog\/en\/implementing-a-state-machine-in-c17\/?category=hard-development&amp;tag=c17-en,embedded-competency-center-en,state-machine-en,stdvariant-en,templates-en\" target=\"_blank\" aria-label=\" (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\">Implementing a State Machine in C++17<\/a><\/li><li><a href=\"https:\/\/sii.pl\/blog\/jak-wprowadzalem-zmiany-do-c20\/?category=development-na-twardo&amp;tag=c,c20,komitet-standaryzacyjny-c,polski-komitet-normalizacyjny\" target=\"_blank\" aria-label=\" (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\">Jak wprowadza\u0142em zmiany do C++20<\/a><\/li><li><a href=\"https:\/\/sii.pl\/blog\/pamiec-transakcyjna\/?category=development-na-twardo&amp;tag=cpp,memory,programowanie-wielowatkowe\" target=\"_blank\" aria-label=\" (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\">Pami\u0119\u0107 transakcyjna<\/a><\/li><\/ul>\n\n\n<div class=\"kk-star-ratings kksr-auto kksr-align-left kksr-valign-bottom\"\n    data-payload='{&quot;align&quot;:&quot;left&quot;,&quot;id&quot;:&quot;19378&quot;,&quot;slug&quot;:&quot;default&quot;,&quot;valign&quot;:&quot;bottom&quot;,&quot;ignore&quot;:&quot;&quot;,&quot;reference&quot;:&quot;auto&quot;,&quot;class&quot;:&quot;&quot;,&quot;count&quot;:&quot;5&quot;,&quot;legendonly&quot;:&quot;&quot;,&quot;readonly&quot;:&quot;&quot;,&quot;score&quot;:&quot;5&quot;,&quot;starsonly&quot;:&quot;&quot;,&quot;best&quot;:&quot;5&quot;,&quot;gap&quot;:&quot;11&quot;,&quot;greet&quot;:&quot;&quot;,&quot;legend&quot;:&quot;5\\\/5 ( votes: 5)&quot;,&quot;size&quot;:&quot;18&quot;,&quot;title&quot;:&quot;Semantyka przenoszenia w C++&quot;,&quot;width&quot;:&quot;139.5&quot;,&quot;_legend&quot;:&quot;{score}\\\/{best} ( {votes}: {count})&quot;,&quot;font_factor&quot;:&quot;1.25&quot;}'>\n            \n<div class=\"kksr-stars\">\n    \n<div class=\"kksr-stars-inactive\">\n            <div class=\"kksr-star\" data-star=\"1\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"2\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"3\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"4\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"5\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n    <\/div>\n    \n<div class=\"kksr-stars-active\" style=\"width: 139.5px;\">\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n    <\/div>\n<\/div>\n                \n\n<div class=\"kksr-legend\" style=\"font-size: 14.4px;\">\n            5\/5 ( votes: 5)    <\/div>\n    <\/div>\n","protected":false},"excerpt":{"rendered":"<p>Efektywne zarzadzanie pami\u0119ci\u0105 jest jednym z g\u0142\u00f3wnych problem\u00f3w podczas tworzenia oprogramowania, kt\u00f3re w za\u0142o\u017ceniach ma by\u0107 szybkie i wydajne. Wyr\u00f3\u017cnia &hellip; <a class=\"continued-btn\" href=\"https:\/\/sii.pl\/blog\/semantyka-przenoszenia-w-c\/\">Continued<\/a><\/p>\n","protected":false},"author":468,"featured_media":19742,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_editorskit_title_hidden":false,"_editorskit_reading_time":7,"_editorskit_is_block_options_detached":false,"_editorskit_block_options_position":"{}","inline_featured_image":false,"footnotes":""},"categories":[1314],"tags":[1621,563,1058],"class_list":["post-19378","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development-na-twardo","tag-semantyka-przenoszenia","tag-embedded","tag-cpp"],"acf":[],"aioseo_notices":[],"republish_history":[],"featured_media_url":"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2022\/08\/Semantyka-przenoszenia-w-C.jpg","category_names":["Development na twardo"],"_links":{"self":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts\/19378"}],"collection":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/users\/468"}],"replies":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/comments?post=19378"}],"version-history":[{"count":2,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts\/19378\/revisions"}],"predecessor-version":[{"id":20065,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts\/19378\/revisions\/20065"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/media\/19742"}],"wp:attachment":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/media?parent=19378"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/categories?post=19378"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/tags?post=19378"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}