Swoje przemyślenia oparłem na "C++ Primer" oraz na artykule Herba Shuttera:
Kilka punktów, które mogą okazać się pomocne w przyszłości.
- std::unique_ptr powinien być zawsze preferowany przed std::shared_ptr
- std::shared_ptr i std::unique_ptr powinno być wybierane, tylko gdy chcemy skorzystać z własnego deletera (std::make_shared tego nie umożliwia), albo gdy adaptujemy stary kod i chcemy zarządzać surowym wskaźnikiem.
- W innych przypadkach powinno się korzystać z std::make_unique i std::make_shared.
- Upraszcza to kod. W jednej instrukcji zawarte jest tworzenie inteligentnego wskaźnika i chowany jest new (std::shared_ptr + new), który może rzucać trudne do wykrycia wyjątki. std::make_shared nas przed tym chroni (w C++17 został zmieniony sposób ewaluacji argumentów funkcji i nie jest to już problemem).
- std::make_shared daje istotne optymalizacyjne usprawnienia. std::shared_ptr jedynie tworzy wskaźnik na zasób którym ma zarządzać (który zostanie stworzony przez new) i nie wie, czy to co mu podlega zostało stworzone specjalnie dla niego, czy ma do czynienia z surowym wskaźnikiem. Zasób taki będzie najprawdopodobniej w innym bloku pamięci + kompilator jak zawsze doda kilka ekstra bajtów, przy alokowaniu.
- Nie testowałem, aczkolwiek ma to sens. Ponieważ std::make_shared stworzy obiekt, a także reference counters w jednym bloku pamięci, pamięć taka będzie zwolniona dopiero, gdy nie będzie już żadnych std::weak_ptr wskazujących na ten obiekt. Tworzenie obiektu za pomocą std::shared_ptr ma tu taką przewagę, że będzie mógł on zwolnić zasób, którym zarządza i pozostawić przy życiu jedynie reference counters dla std::weak_ptr. Jeżeli zarządzany obiekt będzie duży, może się to odbić na wydajności.
Dla dużej ilości małych obiektów, może to istotne przyśpieszyć działanie programu, ponieważ zmniejsza się czas dostępu do cache procesora. Właśnie to chciałem przetestować w swoim teście.
#include <iostream> #include <memory> #include <boost/date_time/posix_time/posix_time.hpp> using namespace boost::posix_time; struct MyObject { MyObject(std::string n): name(n) { } std::string name; }; void fun(const std::shared_ptr<MyObject> &sp) { if (sp->name == "xxx") std::cout << "xxx" << std::endl; } int main() { const ptime time_start = microsec_clock::local_time(); for (int i = 0; i < 100000000; ++i) { #ifdef TEST_SHARED_PTR fun(std::shared_ptr<MyObject>(new MyObject("shared"))); #else fun(std::make_shared<MyObject>("make_shared")); #endif } const ptime time_stop = microsec_clock::local_time(); std::cout << "Time: " << time_start - time_stop << std::endl; return 0; }Kompilacja.
$ g++ main.cpp -std=c++11 -O2 -DTEST_SHARED_PTRPoniżej zestawienie wyników dla gcc i clang-a. Zrobiłem też testy bez użycia flag optymalizacyjnych (-O2) i co ciekawe wyniki były zupełnie odwrotne! Nie wynikałem już w przyczynę tego stanu rzeczy. Może kiedyś rozwikłam tą zagadkę.
Brak komentarzy:
Prześlij komentarz