Różnica polega na tym, że std::make_shared
wykonuje jeden przydział sterty, podczas gdy wywoływanie std::shared_ptr
konstruktora wykonuje dwa.
Gdzie mają miejsce alokacje sterty?
std::shared_ptr
zarządza dwoma podmiotami:
- blok kontrolny (przechowuje metadane, takie jak liczba zliczeń, kasowanie danych z kasowaniem typu itp.)
- obiekt zarządzany
std::make_shared
wykonuje pojedynczą alokację sterty z uwzględnieniem miejsca potrzebnego zarówno dla bloku sterującego, jak i danych. W innym przypadku new Obj("foo")
wywołuje alokację sterty dla zarządzanych danych, a std::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
void F(const std::shared_ptr<Lhs> &lhs, const std::shared_ptr<Rhs> &rhs) { /* ... */ }
F(std::shared_ptr<Lhs>(new Lhs("foo")),
std::shared_ptr<Rhs>(new Rhs("bar")));
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 do std::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ć.
auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
F(lhs, rhs);
Preferowanym sposobem rozwiązania tego jest oczywiście użycie std::make_shared
zamiast tego.
F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));
Aktualizacja II: Wada std::make_shared
Cytując komentarze Casey :
Ponieważ istnieje tylko jeden przydział, pamięci osoby, której cel jest przyznany, nie można zwolnić, dopóki blok kontrolny nie będzie już używany. A weak_ptr
może utrzymywać blok kontrolny przy życiu przez czas nieokreślony.
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. Dla lock
). 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óki shared_ptr
liczba i weak_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 ma shared_ptr
s lub weak_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
i shared_ptr
konstruktora. Następnie zwalniamy pamięć dla zarządzanego obiektu (może wcześniej), gdy nie ma shared_ptr
żywych, i zwalniamy pamięć dla bloku kontrolnego (może później), gdy nie ma weak_ptr
żywych.