Odpowiedzi:
Umożliwia uzyskanie prawidłowej shared_ptr
instancji this
, gdy wszystko, co masz, to this
. Bez niego nie byłoby sposobu, aby dostać się shared_ptr
do this
, chyba że masz go już jako członek. Ten przykład z dokumentacji doładowania dla enable_shared_from_this :
class Y: public enable_shared_from_this<Y>
{
public:
shared_ptr<Y> f()
{
return shared_from_this();
}
}
int main()
{
shared_ptr<Y> p(new Y);
shared_ptr<Y> q = p->f();
assert(p == q);
assert(!(p < q || q < p)); // p and q must share ownership
}
Metoda f()
zwraca poprawną wartość shared_ptr
, mimo że nie miała instancji elementu. Pamiętaj, że nie możesz tego po prostu zrobić:
class Y: public enable_shared_from_this<Y>
{
public:
shared_ptr<Y> f()
{
return shared_ptr<Y>(this);
}
}
Współdzielony wskaźnik, który to zwróci, będzie miał inną liczbę odniesień niż „właściwy”, a jeden z nich straci i utrzyma wiszące odniesienie, gdy obiekt zostanie usunięty.
enable_shared_from_this
stał się częścią standardu C ++ 11. Możesz także pobrać go stamtąd, a także z doładowania.
std::shared_ptr
konstruktora na surowym wskaźniku, jeśli dziedziczy std::enable_shared_from_this
. Nie wiem, czy semantyka wzmocnienia została zaktualizowana w celu obsługi tego.
std::shared_ptr
obiektu, który jest już zarządzany przez inny obiekt std::shared_ptr
, nie sprawdzi wewnętrznej słabej referencji przechowywanej wewnętrznie, a tym samym doprowadzi do nieokreślonego zachowania”. ( en.cppreference.com/w/cpp/memory/enable_shared_from_this )
shared_ptr<Y> q = p
?
std::make_shared<T>
.
z artykułu dr Dobbs na temat słabych wskaźników, myślę, że ten przykład jest łatwiejszy do zrozumienia (źródło: http://drdobbs.com/cpp/184402026 ):
... taki kod nie działa poprawnie:
int *ip = new int;
shared_ptr<int> sp1(ip);
shared_ptr<int> sp2(ip);
Żaden z dwóch shared_ptr
obiektów nie wie o drugim, więc oba będą próbowały uwolnić zasób, gdy zostaną zniszczone. To zwykle prowadzi do problemów.
Podobnie, jeśli funkcja członka potrzebuje shared_ptr
obiektu będącego właścicielem obiektu, do którego jest wywoływana, nie może po prostu utworzyć obiektu w locie:
struct S
{
shared_ptr<S> dangerous()
{
return shared_ptr<S>(this); // don't do this!
}
};
int main()
{
shared_ptr<S> sp1(new S);
shared_ptr<S> sp2 = sp1->dangerous();
return 0;
}
Ten kod ma ten sam problem, co we wcześniejszym przykładzie, chociaż w bardziej subtelnej formie. Podczas budowy shared_pt
obiekt r sp1
jest właścicielem nowo przydzielonego zasobu. Kod wewnątrz funkcji S::dangerous
składowej nie wie o tym shared_ptr
obiekcie, więc shared_ptr
obiekt, który zwraca, różni się od niego sp1
. Kopiowanie nowego shared_ptr
obiektu do sp2
nie pomaga; gdy sp2
wyjdzie poza zakres, zwolni zasób, a gdy sp1
wyjdzie poza zakres, ponownie zwolni zasób.
Sposobem uniknięcia tego problemu jest użycie szablonu klasy enable_shared_from_this
. Szablon przyjmuje jeden argument typu szablonu, który jest nazwą klasy definiującej zarządzany zasób. Ta klasa musi z kolei pochodzić publicznie z szablonu; lubię to:
struct S : enable_shared_from_this<S>
{
shared_ptr<S> not_dangerous()
{
return shared_from_this();
}
};
int main()
{
shared_ptr<S> sp1(new S);
shared_ptr<S> sp2 = sp1->not_dangerous();
return 0;
}
Gdy to zrobisz, pamiętaj, że obiekt, do którego dzwonisz, shared_from_this
musi być własnością shared_ptr
obiektu. To nie zadziała:
int main()
{
S *p = new S;
shared_ptr<S> sp2 = p->not_dangerous(); // don't do this
}
shared_ptr<S> sp1(new S);
mówiąc , zamiast tego może być preferowane użycie shared_ptr<S> sp1 = make_shared<S>();
, patrz na przykład stackoverflow.com/questions/18301511/…
shared_ptr<S> sp2 = p->not_dangerous();
ponieważ pułapką jest to, że musisz utworzyć shared_ptr w normalny sposób, zanim zadzwonisz shared_from_this()
po raz pierwszy! Naprawdę łatwo się pomylić! W wersjach wcześniejszych niż C ++ 17 UB może wywoływać shared_from_this()
przed utworzeniem dokładnie jednego pliku shared_ptr w normalny sposób: auto sptr = std::make_shared<S>();
lub shared_ptr<S> sptr(new S());
. Na szczęście od C ++ 17 robienie tego rzuci.
S* s = new S(); shared_ptr<S> ptr = s->not_dangerous();
<- Dozwolone jest wywoływanie shared_from_this tylko na wcześniej współużytkowanym obiekcie, tj. Na obiekcie zarządzanym przez std :: shared_ptr <T>. W przeciwnym razie zachowanie jest niezdefiniowane (dopóki C ++ 17) nie zostanie wyrzucone std :: bad_weak_ptr (przez konstruktora shared_ptr z domyślnie skonstruowanego słaby_this) (od C ++ 17). . Tak więc rzeczywistość jest taka, że należy ją nazywać always_dangerous()
, ponieważ potrzebujesz wiedzy, czy została już udostępniona, czy nie.
Oto moje wyjaśnienie z perspektywy orzechów (pierwsza odpowiedź nie „kliknęła” ze mną). * Zauważ, że jest to wynik badania źródła shared_ptr i enable_shared_from_thth, który jest dostarczany z Visual Studio 2012. Być może inne kompilatory implementują enable_shared_from_this inaczej inaczej ... *
enable_shared_from_this<T>
dodaje prywatną weak_ptr<T>
instancję, do T
której przypisano „ jedną prawdziwą liczbę referencji ” dla instancji T
.
Tak więc, kiedy po raz pierwszy tworzysz shared_ptr<T>
nowy na T *, wewnętrzny słaby_ptr T * jest inicjalizowany z przeliczeniem 1. Nowy w shared_ptr
zasadzie się na to powraca weak_ptr
.
T
następnie w swoich metodach może wywoływać, shared_from_this
aby uzyskać instancję shared_ptr<T>
tego zaplecza na tej samej wewnętrznie przechowywanej liczbie referencji . W ten sposób zawsze masz jedno miejsce, w którym T*
przechowywana jest liczba odwołań, a nie wiele shared_ptr
wystąpień, które nie znają się nawzajem, i każdy uważa, że to one są shared_ptr
odpowiedzialne za przeliczanie T
i usuwanie go, gdy ich odwołanie -licznik osiąga zero.
So, when you first create...
to, że jest to wymóg (jak mówisz, słaby_ptr nie jest inicjowany, dopóki nie przekażesz wskaźnika obiektów do ctora shared_ptr!), A to wymaganie jest tam, gdzie rzeczy mogą się potwornie źle pójść, jeśli jesteś nieostrożny. Jeśli przed wywołaniem nie utworzysz pliku shared_ptr shared_from_this
, otrzymasz UB - podobnie, jeśli utworzysz więcej niż jeden plik shared_ptr, otrzymasz również UB. Musisz jakoś upewnić się, że utworzyłeś shared_ptr dokładnie raz.
enable_shared_from_this
kruchego” jest na początku krucha, ponieważ chodzi o to, aby móc uzyskać shared_ptr<T>
od T*
, ale w rzeczywistości, kiedy dostajesz wskaźnik T* t
, generalnie nie jest bezpiecznie zakładać, że cokolwiek jest już udostępnione lub nie, i błędne zgadywanie to UB.
Zauważ, że użycie boost :: intrusive_ptr nie cierpi z powodu tego problemu. Jest to często wygodniejszy sposób na obejście tego problemu.
enable_shared_from_this
umożliwia pracę z interfejsem API, który specjalnie akceptuje shared_ptr<>
. Moim zdaniem taki interfejs API zwykle robi „Zrób to źle” (ponieważ lepiej jest pozwolić, aby coś wyżej w stosie posiadało pamięć), ale jeśli jesteś zmuszony do pracy z takim interfejsem API, jest to dobra opcja.
Dokładnie tak samo jest w c ++ 11 i późniejszych wersjach: od tego momentu ma możliwość powrotu this
jako wskaźnik wspólnythis
daje surowy wskaźnik.
innymi słowy, pozwala zmienić kod w ten sposób
class Node {
public:
Node* getParent const() {
if (m_parent) {
return m_parent;
} else {
return this;
}
}
private:
Node * m_parent = nullptr;
};
zaangażowany w to:
class Node : std::enable_shared_from_this<Node> {
public:
std::shared_ptr<Node> getParent const() {
std::shared_ptr<Node> parent = m_parent.lock();
if (parent) {
return parent;
} else {
return shared_from_this();
}
}
private:
std::weak_ptr<Node> m_parent;
};
shared_ptr
. Możesz zmienić interfejs, aby upewnić się, że tak jest.
std::shared_ptr<Node> getParent const()
normalnie odsłoniłbym to jako NodePtr getParent const()
zamiast. Jeśli absolutnie potrzebujesz dostępu do wewnętrznego surowego wskaźnika (najlepszy przykład: radzenie sobie z biblioteką C), jest std::shared_ptr<T>::get
na to coś, o czym nienawidzę wspominać, ponieważ używam tego surowego wskaźnika zbyt wiele razy z niewłaściwego powodu.
Innym sposobem jest dodanie weak_ptr<Y> m_stub
członka do class Y
. Następnie napisz:
shared_ptr<Y> Y::f()
{
return m_stub.lock();
}
Przydatne, gdy nie możesz zmienić klasy, z której wywodzisz (np. Rozszerzając bibliotekę innych osób). Nie zapomnij zainicjować członka, np. Przezm_stub = shared_ptr<Y>(this)
, że jest on ważny nawet podczas konstruktora.
Jest OK, jeśli istnieje więcej kodów pośredniczących w hierarchii dziedziczenia, nie zapobiegnie to zniszczeniu obiektu.
Edycja: Jak poprawnie wskazał nobar użytkownika, kod zniszczyłby obiekt Y po zakończeniu przypisania i zniszczeniu zmiennych tymczasowych. Dlatego moja odpowiedź jest nieprawidłowa.
shared_ptr<>
czegoś, co nie usunie jego pointee, jest to przesada. Możesz po prostu powiedzieć, return shared_ptr<Y>(this, no_op_deleter);
gdzie no_op_deleter
jest obiekt jednoargumentowy, który Y*
nic nie bierze i nie robi.
m_stub = shared_ptr<Y>(this)
zbuduje i natychmiast zniszczy tymczasowy share_ptr z tego. Po zakończeniu tej instrukcji this
zostaną usunięte, a wszystkie kolejne odniesienia będą zwisały.
enable_shared_from_this
, zachowuje ona weak_ptr
swoją wartość (wypełnioną przez ctor), zwracaną jako shared_ptr
kiedy zadzwonisz shared_from_this
. Innymi słowy, powielasz to, co enable_shared_from_this
już zapewnia.