{"id":308,"date":"2018-09-22T16:59:35","date_gmt":"2018-09-22T14:59:35","guid":{"rendered":"http:\/\/www.kompikownia.pl\/?p=308"},"modified":"2018-11-22T20:59:31","modified_gmt":"2018-11-22T19:59:31","slug":"inteligentne-wskazniki-unique_ptr","status":"publish","type":"post","link":"https:\/\/www.kompikownia.pl\/index.php\/2018\/09\/22\/inteligentne-wskazniki-unique_ptr\/","title":{"rendered":"unique_ptr &#8211; inteligentne wska\u017aniki"},"content":{"rendered":"<span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">Czas czytania:<\/span> <span class=\"rt-time\">&lt; 1<\/span> <span class=\"rt-label rt-postfix\">minut\u0119<\/span><\/span><p>Wielokrotnie tworz\u0105c jak\u0105\u015b aplikacj\u0119 w C\/C++ mia\u0142e\u015b pewnie problem ze wska\u017anikami. To jest jedna z najwi\u0119kszych zalet C++, a jednocze\u015bnie najgorsze przekle\u0144stwo. Wska\u017aniki pozwalaj\u0105 na bezpo\u015bredni\u0105 kontrol\u0119 nad przydzia\u0142em pami\u0119ci, a jednocze\u015bnie ich u\u017cycie mo\u017ce spowodowa\u0107 powstanie trudnych do wykrycia b\u0142\u0119d\u00f3w. Osoby rozwijaj\u0105ce standard C++ wiedzia\u0142y o tym problemie. Po kilku latach prac nad nowymi standardami powsta\u0142o rozwi\u0105zanie praktycznie wszystkich problem\u00f3w raw-pointer\u00f3w. Sposobem na pokonanie wszelkich problem\u00f3w z \u201esurowymi wska\u017anikami\u201d s\u0105 \u201einteligentne wska\u017aniki\u201d, kt\u00f3rymi zajmiemy si\u0119 w tym artykule.<\/p>\n<p>Nowe mo\u017cliwo\u015bci w zakresie wska\u017anik\u00f3w doda\u0142 standard C++11. C++14 natomiast je rozszerzy\u0142. Powsta\u0142o kilka rodzaj\u00f3w inteligentnych wska\u017anik\u00f3w. Wszystkie s\u0105 zadeklarowane w pliku nag\u0142\u00f3wkowym memory. Powiniene\u015b tak\u017ce doda\u0107 opcj\u0119 kompilacji: <strong>\u201e-std=c++14\u201d<\/strong> w ustawieniach swojego IDE. \u00a0Jeste\u015b gotowy? Zaczynamy \ud83d\ude42 Dzisiaj poznamy pierwszy z nich \u2013 <strong>unique_ptr<\/strong><\/p>\n<h1>Unique_ptr &#8211; wska\u017anik unikalny<\/h1>\n<p>Unique_ptr jest najprostszym z \u201einteligentnych wska\u017anik\u00f3w\u201d. Posiada on \u201ena w\u0142asno\u015b\u0107\u201d stworzony dynamicznie obiekt. W momencie, kiedy wska\u017anik jest niszczony, niszczony jest tak\u017ce wskazywany przez niego obiekt. Dzieje si\u0119 tak np.: po zako\u0144czeniu funkcji\/metody, w kt\u00f3rej zadeklarowany jest dany wska\u017anik. Obiekt jest usuwany tak\u017ce wtedy, kiedy do wska\u017anika przechowuj\u0105cego ju\u017c jaki\u015b obiekt przypiszemy inny obiekt.<\/p>\n<h2>Prze\u0142amujemy lody &#8211; make_unique<\/h2>\n<p><b>Kod 1<\/b><\/p>\n<pre lang=\"cpp\" line=\"1\">\r\n#include <iostream>\r\n\r\nusing namespace std;\r\nclass fooObject {\r\n    public:\r\n    fooObject() {\r\n        cout<<\"Konstruktor fooObject\"<<endl;\r\n    }\r\n    ~fooObject() {\r\n        cout<<\"Destruktor fooObject\"<<endl;\r\n    }\r\n};\r\nint main()\r\n{\r\n    fooObject* object = new fooObject();\r\n    delete object;\r\n}\r\n<\/pre>\n<p><b>Kod II<\/b><\/p>\n<pre lang=\"cpp\" line=\"1\">\r\n#include <iostream>\r\n#include <memory>\r\nusing namespace std;\r\n\r\nclass fooObject {\r\n    public:\r\n    fooObject() {\r\n        cout<<\"Konstruktor fooObject\"<<endl;\r\n    }\r\n    ~fooObject() {\r\n        cout<<\"Destruktor fooObject\"<<endl;\r\n    }\r\n};\r\nint main()\r\n{\r\n    unique_ptr<fooObject> object = make_unique<fooObject>();\r\n    return 0;\r\n}\r\n\r\n<\/pre>\n<p>Zaczniemy od bardzo prostego przyk\u0142adu. Powy\u017cej masz dwa przyk\u0142ady kodu. Kod I jest napisany z u\u017cyciem \u201eraw pointer\u00f3w\u201d. Drugi natomiast \u2013 unique_ptr. Wynik ich dzia\u0142ania jest taki sam \u2013 stworzenie i skasowanie obiektu w funkcji main. Na pewno rzuca ci si\u0119 w oczy brak operator\u00f3w new\/delete w drugim przyk\u0142adzie. Nie jest to potrzebne. Unique_ptr sam zarz\u0105dza cyklem \u017cycia obiektu. Dzi\u0119ki temu programista ma mniej roboty i mniej mo\u017cliwo\u015bci do pope\u0142nienia b\u0142\u0119du \ud83d\ude42 <\/p>\n<p>Co mo\u017cesz jeszcze wywnioskowa\u0107 z powy\u017cszego fragmentu kodu? Ot\u00f3\u017c, nie mo\u017cesz przypisa\u0107 obiektu do unique_ptr za pomoc\u0105 znaku =. Musisz u\u017cy\u0107 specjalnej funkcji: <strong>make_unique<\/strong>. Z czego to wynika? Unique_ptr, jak ju\u017c wspomina\u0142em wcze\u015bniej, nie pozwala na kopiowanie obiektu. U\u017cywaj\u0105c operatora przypisania, mogliby\u015bmy stworzy\u0107 wi\u0119cej ni\u017c jedn\u0105 kopi\u0119 obiektu, co w wypadku unique_ptr jest sytuacj\u0105 niedopuszczaln\u0105. U\u017cywaj\u0105c funkcji <strong>make_unique<\/strong>, tworzymy \"unikalny\" egzemplarz obiektu, kt\u00f3ry jest nast\u0119pnie przejmowany i zarz\u0105dzany przez inteligentny wska\u017anik.<br \/>\nIstnieje mo\u017cliwo\u015b\u0107 przypisania unique_ptr zwyk\u0142ego wska\u017anika, co pokazuje poni\u017cszy przyk\u0142ad:<\/p>\n<pre lang=\"cpp\" line=\"1\">\r\nunique_ptr<int> wsk(new int(6));\r\n<\/pre>\n<p>Nie jest to metoda polecana.<\/p>\n<h2>Kopiowanie!=przenoszenie - move<\/h2>\n<p>Wiemy ju\u017c jak tworzy\u0107 unique_ptry. Dowiedzmy si\u0119 teraz, jak je przenosi\u0107. S\u0142u\u017cy do tego move. Move zmienia \u201ew\u0142a\u015bciciela\u201d obiektu. Unique_ptr, jak sama nazwa wskazuje, jest \u201eunikalnym\u201d wska\u017anikiem. Nie mo\u017cemy wi\u0119c go skopiowa\u0107 (Wyra\u017cenie wskaznik1=wskaznik2 nie skompiluje si\u0119). Mo\u017cemy wykona\u0107 tylko operacj\u0119 przeniesienia. Sp\u00f3jrz na poni\u017cszy przyk\u0142ad:<\/p>\n<pre lang=\"cpp\" line=\"1\">\r\n#include <iostream>\r\n#include <memory>\r\n\r\nusing namespace std;\r\nstruct seriousStructure {\r\n    float number1;\r\n    int number2;\r\n};\r\nint main()\r\n{\r\n    unique_ptr<seriousStructure> object = make_unique<seriousStructure>();\r\n    object->number1 = 3.4;\r\n    object->number2 = 5;\r\n    unique_ptr<seriousStructure> object2;\r\n    object2 = move(object);\r\n    return 0;\r\n}\r\n\r\n<\/pre>\n<p>Mamy struktur\u0119 seriousStructure, kt\u00f3r\u0105 zapisujemy w unique_ptr object i wype\u0142niamy. Nast\u0119pnie przenosimy object do object2. Wska\u017anik object po tej operacji przyjmuje warto\u015b\u0107 nullptr.<br \/>\nSprawd\u017a sam! Zmodyfikuj powy\u017cszy program tak, aby po operacji przeniesienia odczytywa\u0142 zawarto\u015b\u0107 obydwu p\u00f3l struktury ze wska\u017anika object2. Spr\u00f3buj tak\u017ce na samym ko\u0144cu odwo\u0142a\u0107 si\u0119 do wska\u017anika object aby przekona\u0107 si\u0119 na w\u0142asnej sk\u00f3rze o tym, \u017ce przyj\u0105\u0142 on warto\u015b\u0107 nullptr. <\/p>\n<h2>Unique_ptr jako argument funkcji<\/h2>\n<p>Wska\u017aniki unique_ptr mo\u017cemy przekazywa\u0107 jako argumenty funkcji\/metod. Mo\u017cemy zrobi\u0107 to na dwa sposoby. Pierwszy ze sposob\u00f3w to przeniesienie obiektu do funkcji za pomoc\u0105 metody move. Mo\u017cemy tak\u017ce przekaza\u0107 unique_ptr przez referencj\u0119. Pami\u0119tajmy, \u017ce jak u\u017cyjemy funkcji move, to nasz oryginalny wska\u017anik jest nullowany.<\/p>\n<pre lang=\"cpp\" line=\"1\">\r\n#include <iostream>\r\n#include <memory>\r\n#include <assert.h>\r\n\r\nusing namespace std;\r\nvoid foo(unique_ptr<int>& arg) {\r\n    cout<<\"foo: \"<<*arg<<endl;\r\n    \/\/ zmie\u0144my warto\u015b\u0107 arg\r\n    *arg = 7;\r\n}\r\nvoid foo2(unique_ptr<int> arg) {\r\n    cout<<\"foo2: \"<<*arg<<endl;\r\n}\r\nint main()\r\n{\r\n    unique_ptr<int> number = make_unique<int>(5);\r\n    foo(number);\r\n    foo2(move(number));\r\n    assert(number); \/\/ Asercja prawdziwa - number jest r\u00f3wne null\r\n    return 0;\r\n}\r\n\r\n<\/pre>\n<p>W powy\u017cszym kodzie mamy dwie funkcje. Pierwsza funkcja, foo, przyjmuje unique_ptr typu int. Argument przekazywany jest przez referencj\u0119. Mo\u017cemy zmieni\u0107 warto\u015b\u0107 wskazywan\u0105 przez ten wska\u017anik tak jak by\u015bmy u\u017cywali zwyk\u0142ych. <\/p>\n<p>Foo2 jest innym przyk\u0142adem. Z deklaracji funkcji wynika\u0142oby, \u017ce unique_ptr jest przekazywany przez kopi\u0119. Jak ju\u017c wiemy, unique_ptr jest wska\u017anikiem unikalnym, wi\u0119c nie mo\u017cemy go skopiowa\u0107. Jedynym sposobem na przekazanie argumentu w\u00a0takiej sytuacji jest przeniesienie w\u0142asno\u015bci za pomoc\u0105 operatora move. Wtedy oryginalny wska\u017anik \u2013 number ma warto\u015b\u0107 nullptr. Wychwyci to asercja, kt\u00f3ra znajduje si\u0119 na ko\u0144cu powy\u017cszego przyk\u0142adu.<\/p>\n<h2>Praktyczny przyk\u0142ad - stos oparty na unique_ptrach<\/h2>\n<pre lang=\"cpp\" line=\"1\">\r\n#include <iostream>\r\n#include <memory>\r\nusing namespace std;\r\nclass stackNode {\r\n    public:\r\n    int actual;\r\n    unique_ptr<stackNode> prev;\r\n};\r\nclass stack {\r\n    public:\r\n    void push(int number) {\r\n        if(firstStackNode==nullptr) {\r\n            unique_ptr<stackNode> newStackNode = make_unique<stackNode>();\r\n            newStackNode->actual = number;\r\n            firstStackNode = move(newStackNode);\r\n        }\r\n        else {\r\n            unique_ptr<stackNode> newStackNode = make_unique<stackNode>();\r\n            newStackNode->actual = number;\r\n            newStackNode->prev = move(firstStackNode);\r\n            firstStackNode = move(newStackNode);\r\n        }\r\n    }\r\n    int pop() {\r\n        if(firstStackNode==nullptr) return -1;\r\n        else {\r\n            int number = firstStackNode->actual;\r\n            firstStackNode = move(firstStackNode->prev);\r\n            return number;\r\n        }\r\n    }\r\nprivate:\r\n    unique_ptr<stackNode> firstStackNode;\r\n};\r\nint main()\r\n{\r\n    stack st;\r\n    st.push(5);\r\n    st.push(4);\r\n    st.push(6);\r\n    cout<<st.pop()<<\"\\n\";\r\n    cout<<st.pop()<<\"\\n\";\r\n    return 0;\r\n}\r\n<\/pre>\n<p>Powy\u017cej znajduje si\u0119 przyk\u0142ad stosu zbudowanego na bazie unique_ptr\u00f3w. Prawda, \u017ce wygl\u0105da nieziemsko? \u017badnych operacji new, \u017cadnych delete'\u00f3w. Nie martwisz si\u0119 o zwolnienie pami\u0119ci po zdj\u0119ciu obiektu ze stosu. Same zalety. Kod tak\u017ce wygl\u0105da o wiele bardziej przejrzysto. <\/p>\n<p>Zrobi\u0142em par\u0119 uproszcze\u0144 - stos przyjmuje tylko liczby ca\u0142kowite typu int. Nie chc\u0105c dodawa\u0107 obs\u0142ugi wyj\u0105tk\u00f3w ani b\u0142\u0119d\u00f3w, w wypadku braku liczb na stosie metoda pop zwraca warto\u015b\u0107 -1. Stos mo\u017ce wi\u0119c przechowywa\u0107 tylko liczby ca\u0142kowite. Nic nie stoi na przeszkodzie, aby\u015b spr\u00f3bowa\u0142 w\u0142asnymi si\u0142ami go ulepszy\u0107. <\/p>\n<h1>Podsumowuj\u0105c<\/h1>\n<p>Wraz ze standardem C++11\/C++14 powinni\u015bmy jak najrzadziej u\u017cywa\u0107 zwyk\u0142ych wska\u017anik\u00f3w, gdy\u017c wi\u0105\u017ce si\u0119 z nimi du\u017co problem\u00f3w. Unique_ptr sam zarz\u0105dza cyklem \u017cycia obiektu, co niesie ze sob\u0105 du\u017c\u0105 ilo\u015b\u0107 zalet. W tym artykule poruszy\u0142em jedynie wierzcho\u0142ek g\u00f3ry lodowej. Niemniej, powy\u017csze wiadomo\u015bci powinny pom\u00f3c zrozumie\u0107 ci zasad\u0119 dzia\u0142ania wska\u017anika unique_ptr i pozwoli\u0107 na zastosowanie go w swoich w\u0142asnych programach.<br \/>\nW ramach \u0107wicze\u0144 mo\u017cesz spr\u00f3bowa\u0107 zaimplementowa\u0107 w podobny spos\u00f3b kolejk\u0119 FIFO.<br \/>\nPliki zawieraj\u0105ce kod \u017ar\u00f3d\u0142owy program\u00f3w zaprezentowanych w tym wpisie znajduj\u0105 si\u0119 <a href=\"https:\/\/github.com\/karol221-10\/blog_apps\/tree\/master\/unique_ptr\">tu<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p><span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">Czas czytania:<\/span> <span class=\"rt-time\">&lt; 1<\/span> <span class=\"rt-label rt-postfix\">minut\u0119<\/span><\/span> Wielokrotnie tworz\u0105c jak\u0105\u015b aplikacj\u0119 w C\/C++ mia\u0142e\u015b pewnie problem ze wska\u017anikami. To jest jedna z najwi\u0119kszych zalet C++, a jednocze\u015bnie najgorsze przekle\u0144stwo. Wska\u017aniki pozwalaj\u0105 na bezpo\u015bredni\u0105 kontrol\u0119 nad przydzia\u0142em pami\u0119ci, &#8230;<\/p>\n","protected":false},"author":1,"featured_media":463,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[31],"tags":[],"_links":{"self":[{"href":"https:\/\/www.kompikownia.pl\/index.php\/wp-json\/wp\/v2\/posts\/308"}],"collection":[{"href":"https:\/\/www.kompikownia.pl\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.kompikownia.pl\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.kompikownia.pl\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.kompikownia.pl\/index.php\/wp-json\/wp\/v2\/comments?post=308"}],"version-history":[{"count":26,"href":"https:\/\/www.kompikownia.pl\/index.php\/wp-json\/wp\/v2\/posts\/308\/revisions"}],"predecessor-version":[{"id":353,"href":"https:\/\/www.kompikownia.pl\/index.php\/wp-json\/wp\/v2\/posts\/308\/revisions\/353"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.kompikownia.pl\/index.php\/wp-json\/wp\/v2\/media\/463"}],"wp:attachment":[{"href":"https:\/\/www.kompikownia.pl\/index.php\/wp-json\/wp\/v2\/media?parent=308"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.kompikownia.pl\/index.php\/wp-json\/wp\/v2\/categories?post=308"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.kompikownia.pl\/index.php\/wp-json\/wp\/v2\/tags?post=308"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}