Po co nam Smart Pointery?

Dzisiajszym wpisem chcialbym otworzyć na blogu temat wskaźników inteligentnych: std::unique_ptr, std::shared_ptr, std::weak_ptr, które znajdziemy w bibliotece <memory> o ile posiadamy kompilator zgodny ze standardem C++11 lub nowszym. 

Aby dobrze wejść w temat smart pointerów, najpierw chciałbym napisać o wskaźnikach surowych (raw pointers). 

Raw pointerów używamy głównie do zarządzania zasobami, które są alokowane dynamicznie (czyli na stercie [ang. free store, heap], za pomocą operatora new).

Dzięki nim, możemy kontrolować czas życia obiektu- niesie to za sobą wiele możliwości ale i kilka zagrożeń. Przejdźmy to przykładu. 

Otwieramy kod, który został napisany powiedzmy dekadę temu i spotykamy funkcję, która przyjmuje jako argument wskaźnik:

 void function(Foo * foo); 

I mamy problem. W zasadzie już na starcie, bo niewiele możemy w tym momencie o tym wskaźniku powiedzieć.
Nie wiemy czy jest to wskaźnik do jednego elementu, czy może do tablicy. 

Nie wiemy czy wraz ze wskaźnikiem otrzymujemy obiekt na właność i zarządzamy cyklem jego życia, czy też powinniśmy jedynie go zmodyfikować? Jeżeli zwolnimy zasób, ktoś inny może się do niego odwołać w dalszej częsci programu i mamy crash. Jeżeli tego nie zrobimy, a powinniśmy, to mamy wyciek pamięci.

Co do samego niszczenie obiektu to nie wiemy czy obiekt powinien być zniszczony w standrdowy sposób (destruktor) czy może przed tą czynnością powinniśmy wykonać jakieś dodatkowe operacje (np wykonanie określonych logów), a może to w ogóle jest tablica a nie pojedynczy element i użycie błędnego operatora delete da nam undefined behaviour? Nie wiemy- a co najgorsze czasem ta pomyłka w ogóle nie da się nam we znaki przy kompilacji. 

Nie wiemy także, czy zasób na który wskazuje wskaźnik nadal istnieje (może ktoś go już zniszczył?). W takim przypadku zdereferowanie wskaźnika zaskutuje crashem. Podobnie w przypadku, gdy wskaźni będzie zerowy.

Jeden mały wskaźnik i tyle pytań, które choć w prostym przypadku wydają się jasne, to w złożonej aplikacji, po długim czasie i jej dalszej rozbudowie, mogą rodzić problemy.

Wymiana wad raw pointerów nie ma bynajmniej służyć dyskredytacji, a jedynie ma ukazać ich naturę- są potężne, dają nam wiele możliwości, ale jednocześnie wraz z możliwościami otrzymujemy pewną odpowiedzialność, niekoniecznie otrzymując wszystkie potrzebne nam informacje. 
W rezultacie doświadczeni gracze wiedzą jak się z nimi obchodzić, natomiast początkujący lubią się na nich przejechać. Choć zapewne czasem i Ci pierwsi muszą spędzić sporo czasu nad analizowaniem innych partii kodu by odpowiedzieć na wszystkie zadane wyżej pytania.

W celu lepszej pracy chcemy zyskać odpowiedzi na jak najwięcej z tych pytań, jednocześnie nie chcemy zrezygnować z kontroli nad zasobami na które wskazujemy. Chcemy odpowiedzi, natomiast nie chcemy za to zbyt wiele płacić. I właśnie tutaj pojawiają się smart pointery, jako pewien kompromis między naszym bezpieczeństwem i dostarczaną wiedzą, a wydajnością i kontrolą nad zasobami.

W dalszym wpisach omówimy 3 smart pointery: std::unique_ptr, std::shared_ptr, std::weak_ptr. Postaramy się zobaczyć co nam oferują w porównaniu do wskaźników surowych, oraz jakie musimy ponieść związane z tym koszta. Słowem końca wypada wspomnieć, że jeszcze zanim weszły one do standardu języka mogliśmy tam już znaleźć pierwszy smart pointer jakim był std::auto_ptr. Nie obsługiwał on jednak semantyki przenoszenia, co sprawiało pewne problemy z jego używaniem i ostatecznie został on zastąpiony przez std::unique_ptr. Współcześnie nie znam powodu, dla którego mielibyśmy go nadal używać, to też pominę go podczas omawiania pozostałych wskaźników inteligentnych.

Dalsze wpisy już niebawem, 
do zobaczenia niebawem!