Downcasting shared_ptr <Base> do shared_ptr <Derived>?

102

Aktualizacja: shared_ptr w tym przykładzie jest podobne do tego w Boost, ale nie obsługuje shared_polymorphic_downcast (ani dynamic_pointer_cast lub static_pointer_cast w tym przypadku)!

Próbuję zainicjować udostępniony wskaźnik do klasy pochodnej bez utraty liczby odwołań:

struct Base { };
struct Derived : public Base { };
shared_ptr<Base> base(new Base());
shared_ptr<Derived> derived;

// error: invalid conversion from 'Base* const' to 'Derived*'
derived = base;  

Na razie w porządku. Nie spodziewałem się, że C ++ niejawnie przekonwertuje Base * na Derived *. Jednak chcę, aby funkcjonalność wyrażona w kodzie (czyli utrzymanie liczby odwołań podczas obniżania wskaźnika podstawowego). Moją pierwszą myślą było zapewnienie operatora rzutowania w Base, aby mogła mieć miejsce niejawna konwersja do Derived (dla pedantów: sprawdziłbym, czy rzutowanie w dół jest prawidłowe, nie martw się):

struct Base {
  operator Derived* ();
}
// ...
Base::operator Derived* () {
  return down_cast<Derived*>(this);
}

Cóż, to nie pomogło. Wygląda na to, że kompilator całkowicie zignorował mój operator typecast. Jakieś pomysły, jak mogę sprawić, by przypisanie shared_ptr działało? Za dodatkowe punkty: jaki to rodzaj Base* const? const Base*Rozumiem, ale Base* const? Do czego to się constodnosi w tym przypadku?

Lajos Nagy
źródło
Dlaczego potrzebujesz shared_ptr <Derived> zamiast shared_ptr <Base>?
Bill
3
Ponieważ chcę uzyskać dostęp do funkcji w Derived, których nie ma w Base, bez klonowania obiektu (chcę mieć pojedynczy obiekt, do którego odwołują się dwa wspólne wskaźniki). Swoją drogą, dlaczego operatorzy obsady nie działają?
Lajos Nagy

Odpowiedzi:

109

Możesz użyć dynamic_pointer_cast. Jest obsługiwany przez std::shared_ptr.

std::shared_ptr<Base> base (new Derived());
std::shared_ptr<Derived> derived =
               std::dynamic_pointer_cast<Derived> (base);

Dokumentacja: https://en.cppreference.com/w/cpp/memory/shared_ptr/pointer_cast

Nie polecam też używania operatora rzutowania w klasie bazowej. Niejawne rzutowanie, takie jak to, może stać się źródłem błędów i błędów.

-Aktualizacja: jeśli typ nie jest polimorficzny, std::static_pointer_castmożna użyć.

Massood Khaari
źródło
4
Nie zrozumiałem od pierwszej linijki, że on nie używa std::shared_ptr. Ale z komentarzy do pierwszej odpowiedzi wywnioskowałem, że nie używa doładowania, więc może używa std::shared_ptr.
Massood Khaari,
DOBRZE. Przepraszam. Powinien lepiej wyjaśnić, że używa niestandardowej implementacji.
Massood Khaari
47

Zakładam, że używasz boost::shared_ptr... Myślę, że chcesz dynamic_pointer_castlub shared_polymorphic_downcast.

Jednak wymagają one typów polimorficznych.

jaki to rodzaj Base* const? const Base*Rozumiem, ale Base* const? Do czego to się constodnosi w tym przypadku?

  • const Base *jest zmiennym wskaźnikiem do stałej Base.
  • Base const *jest zmiennym wskaźnikiem do stałej Base.
  • Base * constjest stałym wskaźnikiem do mutable Base.
  • Base const * constjest stałym wskaźnikiem do stałej Base.

Oto minimalny przykład:

struct Base { virtual ~Base() { } };   // dynamic casts require polymorphic types
struct Derived : public Base { };

boost::shared_ptr<Base> base(new Base());
boost::shared_ptr<Derived> derived;
derived = boost::static_pointer_cast<Derived>(base);
derived = boost::dynamic_pointer_cast<Derived>(base);
derived = boost::shared_polymorphic_downcast<Derived>(base);

Nie jestem pewien, czy to było zamierzone, aby Twój przykład tworzył instancję typu podstawowego i rzucał ją, ale służy to ładnemu zilustrowaniu różnicy.

static_pointer_castWola „po prostu zrób to”. Spowoduje to niezdefiniowane zachowanie ( Derived*wskazanie na pamięć przydzieloną i zainicjowaną przez Base) i prawdopodobnie spowoduje awarię lub gorzej. Licznik odwołań basezostanie zwiększony.

dynamic_pointer_castSpowoduje wskaźnik NULL. Liczymy na referencjebase pozostanie niezmieniony.

shared_polymorphic_downcastBędzie miał taki sam wynik jak gipsie statycznym, ale wywoła twierdzenie, niż zdaje się uda i prowadzi do nieokreślonego zachowania. Licznik odwołań basezostanie zwiększony.

Zobacz (martwy link) :

Czasami trudno jest zdecydować, czy użyć, static_castczy dynamic_casti chciałbyś mieć trochę obu światów. Powszechnie wiadomo, że dynamic_cast ma narzut w czasie wykonywania, ale jest bezpieczniejszy, podczas gdy static_cast nie ma żadnego narzutu, ale może po cichu zawieść. Jak fajnie by było, gdybyś mógł używać go shared_dynamic_castw kompilacjach debugowania i shared_static_castkompilacjach wydań. Cóż, taka rzecz jest już dostępna i nazywa się shared_polymorphic_downcast.

Tim Sylvester
źródło
Niestety, Twoje rozwiązanie zależy od funkcji Boost, która została celowo wykluczona z konkretnej implementacji shared_ptr, której używamy (nie pytaj dlaczego). Jeśli chodzi o wyjaśnienie const, ma teraz znacznie więcej sensu.
Lajos Nagy
3
Oprócz zaimplementowania innych shared_ptrkonstruktorów (biorąc static_cast_tagi dynamic_cast_tag), niewiele można zrobić. Cokolwiek zrobisz na zewnątrz shared_ptr, nie będzie w stanie zarządzać zwrotem. - W „doskonałym” projekcie obiektowym można zawsze używać typu podstawowego i nigdy nie trzeba wiedzieć, ani obchodzić się z typem pochodnym, ponieważ cała jego funkcjonalność jest ujawniana przez interfejsy klasy bazowej. Być może po prostu musisz ponownie przemyśleć, dlaczego musisz rzucić w dół.
Tim Sylvester
1
@Tim Sylvester: ale C ++ nie jest „doskonałym” językiem OO! :-) down-casts mają swoje miejsce w niedoskonałym języku OO
Steve Folly
4

Jeśli ktoś dotrze tutaj z boost :: shared_ptr ...

W ten sposób możesz obniżyć do pochodnego Boost shared_ptr. Zakładając, że Derived dziedziczy po Base.

boost::shared_ptr<Base> bS;
bS.reset(new Derived());

boost::shared_ptr<Derived> dS = boost::dynamic_pointer_cast<Derived,Base>(bS);
std::cout << "DerivedSPtr  is: " << std::boolalpha << (dS.get() != 0) << std::endl;

Upewnij się, że klasa / struktura „Base” ma co najmniej jedną funkcję wirtualną. Działa również wirtualny destruktor.

Mitendra
źródło