std::shared_ptr<Object> p1 = std::make_shared<Object>("foo");
std::shared_ptr<Object> p2(new Object("foo"));
Wiele postów z Google i Stackoverflow jest na ten temat, ale nie jestem w stanie zrozumieć, dlaczego make_shared
jest bardziej wydajny niż bezpośrednie korzystanie shared_ptr
.
Czy ktoś może mi wyjaśnić krok po kroku sekwencję obiektów utworzonych i operacji wykonanych przez oba, aby móc zrozumieć, jak make_shared
jest skuteczny. Podałem jeden przykład powyżej w celach informacyjnych.
c++
c++11
shared-ptr
Anup Buchke
źródło
źródło
make_shared
możesz pisać,auto p1(std::make_shared<A>())
a p1 będzie miał poprawny typ.Odpowiedzi:
Różnica polega na tym, że
std::make_shared
wykonuje jeden przydział sterty, podczas gdy wywoływaniestd::shared_ptr
konstruktora wykonuje dwa.Gdzie mają miejsce alokacje sterty?
std::shared_ptr
zarządza dwoma podmiotami:std::make_shared
wykonuje pojedynczą alokację sterty z uwzględnieniem miejsca potrzebnego zarówno dla bloku sterującego, jak i danych. W innym przypadkunew Obj("foo")
wywołuje alokację sterty dla zarządzanych danych, astd::shared_ptr
konstruktor wykonuje kolejną dla bloku sterującego.Aby uzyskać więcej informacji, sprawdź uwagi dotyczące implementacji na cppreference .
Aktualizacja I: Bezpieczeństwo wyjątkowe
UWAGA (2019/08/30) : Nie stanowi to problemu od C ++ 17, ze względu na zmiany w kolejności oceny argumentów funkcji. W szczególności każdy argument funkcji jest wymagany do pełnego wykonania przed oceną innych argumentów.
Ponieważ PO wydaje się zastanawiać nad kwestią bezpieczeństwa i wyjątków, zaktualizowałem swoją odpowiedź.
Rozważ ten przykład
Ponieważ C ++ pozwala na dowolną kolejność oceny podwyrażeń, jednym z możliwych porządków jest:
new Lhs("foo"))
new Rhs("bar"))
std::shared_ptr<Lhs>
std::shared_ptr<Rhs>
Załóżmy, że otrzymamy wyjątek zgłoszony w kroku 2 (np. Z wyjątku pamięci,
Rhs
konstruktor zgłosił wyjątek). Następnie tracimy pamięć przydzieloną w kroku 1, ponieważ nic nie będzie miało szansy jej wyczyszczenia. Istotą tego problemu jest to, że surowy wskaźnik nie został natychmiast przekazany dostd::shared_ptr
konstruktora.Jednym ze sposobów, aby to naprawić, jest wykonanie ich w osobnych wierszach, aby to arbitralne zamówienie nie mogło wystąpić.
Preferowanym sposobem rozwiązania tego jest oczywiście użycie
std::make_shared
zamiast tego.Aktualizacja II: Wada
std::make_shared
Cytując komentarze Casey :
Dlaczego instancje
weak_ptr
s utrzymują blok kontrolny przy życiu?Musi istnieć sposób, aby
weak_ptr
s określił, czy zarządzany obiekt jest nadal ważny (np. Dlalock
). Robią to, sprawdzając liczbęshared_ptr
s, która jest właścicielem zarządzanego obiektu, który jest przechowywany w bloku kontrolnym. Powoduje to, że bloki kontrolne są aktywne, dopókishared_ptr
liczba iweak_ptr
liczba nie osiągną 0.Wrócić do
std::make_shared
Ponieważ
std::make_shared
dokonuje pojedynczej alokacji sterty zarówno dla bloku sterującego, jak i obiektu zarządzanego, nie ma możliwości niezależnego zwolnienia pamięci dla bloku sterującego i obiektu zarządzanego. Musimy poczekać, aż możemy uwolnić zarówno blok sterowania i zarządzanego obiektu, który dzieje się dopóki nie mashared_ptr
s lubweak_ptr
S żyje.Załóżmy, że zamiast tego wykonaliśmy dwie alokacje sterty dla bloku sterującego i obiektu zarządzanego za pośrednictwem
new
ishared_ptr
konstruktora. Następnie zwalniamy pamięć dla zarządzanego obiektu (może wcześniej), gdy nie mashared_ptr
żywych, i zwalniamy pamięć dla bloku kontrolnego (może później), gdy nie maweak_ptr
żywych.źródło
make_shared
: ponieważ istnieje tylko jedna alokacja, pamięci osoby, której dane dotyczą, nie można zwolnić, dopóki blok sterowania nie będzie już używany. Aweak_ptr
może utrzymywać blok kontrolny przy życiu przez czas nieokreślony.make_shared
imake_unique
konsekwentnie, nie będziesz posiadał surowych wskaźników i możesz traktować każde wystąpienienew
jako zapach kodu.shared_ptr
i nie maweak_ptr
s, nazywającreset()
nashared_ptr
przykład usunie blok sterowania. Ale to niezależnie od tego, czymake_shared
zostało użyte. Używaniemake_shared
robi różnicę, ponieważ może przedłużyć żywotność pamięci przydzielonej dla zarządzanego obiektu . Kiedyshared_ptr
liczba osiągnie wartość 0, destruktor zarządzanego obiektu jest wywoływany niezależnie odmake_shared
, ale zwolnienie jego pamięci można wykonać tylko wtedy, gdy niemake_shared
był używany. Mam nadzieję, że to wyjaśnia.Współdzielony wskaźnik zarządza zarówno samym obiektem, jak i małym obiektem zawierającym liczbę referencji i inne dane porządkowe.
make_shared
może przydzielić jeden blok pamięci do przechowywania obu tych elementów; zbudowanie współdzielonego wskaźnika ze wskaźnika do już przydzielonego obiektu będzie wymagało przydzielenia drugiego bloku do przechowywania liczby referencji.Oprócz tej wydajności użycie
make_shared
oznacza, że nie musisz w ogóle zajmować sięnew
surowymi wskaźnikami, co zapewnia większe bezpieczeństwo wyjątków - nie ma możliwości zgłoszenia wyjątku po przydzieleniu obiektu, ale przed przypisaniem go do inteligentnego wskaźnika.źródło
Jest inny przypadek, w którym dwie możliwości różnią się, oprócz tych już wspomnianych: jeśli chcesz wywołać niepublicznego konstruktora (chronionego lub prywatnego), make_shared może nie być w stanie uzyskać do niego dostępu, podczas gdy wariant z nowymi działa dobrze .
źródło
new
, w przeciwnym razie skorzystałbymmake_shared
. Oto powiązany pytanie o tym: stackoverflow.com/questions/8147027/... .Jeśli potrzebujesz specjalnego wyrównania pamięci na obiekcie kontrolowanym przez shared_ptr, nie możesz polegać na make_shared, ale myślę, że to jedyny dobry powód, aby go nie używać.
źródło
Widzę jeden problem ze std :: make_shared, nie obsługuje on konstruktorów prywatnych / chronionych
źródło
Shared_ptr
: Wykonuje dwie alokacje stertyMake_shared
: Wykonuje tylko jeden przydział stertyźródło
Jeśli chodzi o wydajność i czas poświęcony na alokację, zrobiłem ten prosty test poniżej, stworzyłem wiele instancji na dwa sposoby (jeden na raz):
Chodzi o to, że użycie make_shared zajęło dwa razy więcej czasu niż użycie nowego. Tak więc przy użyciu nowego istnieją dwa przydziały sterty zamiast jednego przy użyciu make_shared. Może to głupi test, ale czy nie pokazuje, że używanie make_shared zajmuje więcej czasu niż używanie nowego? Oczywiście mówię tylko o wykorzystanym czasie.
źródło
Myślę, że wyjątek dotyczący bezpieczeństwa w odpowiedzi pana mparka jest nadal ważnym problemem. podczas tworzenia share_ptr w następujący sposób: shared_ptr <T> (nowy T), nowy T może się powieść, a alokacja bloku kontrolnego shared_ptr może się nie powieść. w tym scenariuszu nowo przydzielony T wycieknie, ponieważ shared_ptr nie ma możliwości dowiedzenia się, że został utworzony w miejscu i można go bezpiecznie usunąć. czy coś mi brakuje? Nie wydaje mi się, żeby surowsze zasady oceny parametrów funkcji w jakikolwiek sposób pomogły ...
źródło