Przekazywanie shared_ptr <Derived> jako shared_ptr <Base>


93

Jaka jest najlepsza metoda przekazywania a shared_ptrtypu pochodnego do funkcji, która przyjmuje shared_ptrtyp podstawowy?

Generalnie przekazuję shared_ptrje jako odniesienie, aby uniknąć niepotrzebnych kopii:

int foo(const shared_ptr<bar>& ptr);

ale to nie działa, jeśli spróbuję zrobić coś takiego

int foo(const shared_ptr<Base>& ptr);

...

shared_ptr<Derived> bar = make_shared<Derived>();
foo(bar);

mógłbym użyć

foo(dynamic_pointer_cast<Base, Derived>(bar));

ale wydaje się to nieoptymalne z dwóch powodów:

  • dynamic_castWydaje się nieco nadmierne dla prostego pochodzi do podstawy obsady.
  • Jak rozumiem, dynamic_pointer_casttworzy kopię (choć tymczasową) wskaźnika, aby przejść do funkcji.

Czy jest lepsze rozwiązanie?

Aktualizacja dla potomnych:

Okazało się, że był to problem z brakującym plikiem nagłówkowym. Poza tym to, co próbowałem tutaj zrobić, jest uważane za anty-wzór. Ogólnie,

  • Funkcje, które nie wpływają na czas życia obiektu (tj. Obiekt pozostaje ważny przez czas trwania funkcji) powinny przyjmować zwykłe odwołanie lub wskaźnik, np int foo(bar& b).

  • Funkcje, które konsumują obiekt (czyli są końcowymi użytkownikami danego obiektu) powinny przyjmować unique_ptrwartość, np int foo(unique_ptr<bar> b). Wzywający powinni std::movepodać wartość do funkcji.

  • Funkcje wydłużające żywotność obiektu powinny przyjmować shared_ptrwartość, np int foo(shared_ptr<bar> b). Obowiązuje zwyczajowa rada, aby unikać odwołań cyklicznych .

Szczegóły w artykule Herba Suttera Back to Basics .


8
Dlaczego chcesz zdać shared_ptr? Dlaczego brak odniesienia do stałej baru?
— ipc

2
Każda dynamicobsada jest potrzebna tylko do obniżenia. Również przekazanie wskaźnika pochodnego powinno działać dobrze. shared_ptrUtworzy nowy z tym samym refcount (i zwiększy go) i wskaźnikiem do bazy, która następnie zostanie powiązana z referencją const. Ponieważ jednak już bierzesz referencje, nie rozumiem, dlaczego shared_ptrw ogóle chcesz je wziąć . Weź Base const&i zadzwoń foo(*bar).
— Xeo

@Xeo: Przekazywanie wskaźnika pochodnego (tj. foo(bar)) Nie działa, przynajmniej w MSVC 2010.
— Matt Kline

1
Co masz na myśli mówiąc „oczywiście nie działa”? Kod kompiluje się i zachowuje poprawnie; pytasz, jak uniknąć tworzenia tymczasowego, shared_ptraby przejść do funkcji? Jestem prawie pewien, że nie da się tego uniknąć.
— Mike Seymour

1
@Seth: Nie zgadzam się. Myślę, że jest powód, aby przekazywać wspólny wskaźnik według wartości, a nie ma powodu, aby przekazywać wspólny wskaźnik przez odniesienie (a wszystko to bez popierania niepotrzebnych kopii). Rozumowanie tutaj stackoverflow.com/questions/10826541/…
— R. Martinho Fernandes

Odpowiedzi:


47

Chociaż Basei Derivedsą kowariantne, a surowe wskaźniki do nich będą działać odpowiednio shared_ptr<Base>i nieshared_ptr<Derived> są kowariantne. Jest to poprawny i najprostszy sposób rozwiązania tego problemu.dynamic_pointer_cast

( Edycja: static_pointer_cast byłoby bardziej odpowiednie, ponieważ rzutujesz z wersji pochodnej do bazy, co jest bezpieczne i nie wymaga sprawdzania w czasie wykonywania. Zobacz komentarze poniżej).

Jeśli jednak twoja foo()funkcja nie chce brać udziału w wydłużaniu czasu życia (a raczej brać udział we współwłasności obiektu), to najlepiej jest zaakceptować a const Base&i usunąć odniesienie do shared_ptrpodczas przekazywania go foo().

void foo(const Base& base);
[...]
shared_ptr<Derived> spDerived = getDerived();
foo(*spDerived);

Na marginesie, ponieważ shared_ptrtypy nie mogą być kowariantne, reguły niejawnych konwersji w kowariantnych typach zwracanych nie mają zastosowania podczas zwracania typów shared_ptr<T>.


39
Nie są kowariantne, ale shared_ptr<Derived>można je niejawnie konwertować na shared_ptr<Base>, więc kod powinien działać bez rzucania shenaniganów.
— Mike Seymour

9
Um, shared_ptr<Ty>ma konstruktor, który pobiera a shared_ptr<Other>i wykonuje odpowiednią konwersję, jeśli Ty*jest niejawnie konwertowany na Other*. A jeśli potrzebna jest obsada, static_pointer_castto jest odpowiednia, a nie dynamic_pointer_cast.
— Pete Becker

To prawda, ale nie z jego parametrem odniesienia, jak w pytaniu. Mimo wszystko musiałby zrobić kopię. Ale jeśli używa shared_ptrodwołań do, aby uniknąć liczenia odwołań, to naprawdę nie ma dobrego powodu, aby używać shared_ptrw pierwszej kolejności. Najlepiej const Base&zamiast tego użyć .
— Bret Kuhns

@PeteBecker Zobacz mój komentarz do Mike'a na temat konstruktora konwersji. Szczerze nie wiedziałem static_pointer_cast, dzięki.
— Bret Kuhns

1
@TanveerBadar Nie jestem pewien. Być może nie udało się tego skompilować w 2012 roku? (w szczególności przy użyciu programu Visual Studio 2010 lub 2012). Ale masz absolutną rację, kod OP powinien absolutnie się kompilować, jeśli pełna definicja / publicznie wyprowadzonej / klasy jest widoczna dla kompilatora.
— Bret Kuhns

32

Dzieje się tak również, jeśli zapomniałeś określić dziedziczenie publiczne w klasie pochodnej, tj. Jeśli tak jak ja napiszesz to:

class Derived : Base
{
};

classdotyczy parametrów szablonu; structsłuży do definiowania klas. (To co najwyżej 45% żartu.)
— Davis Herring,

To zdecydowanie należy uznać za rozwiązanie, nie ma potrzeby obsady, ponieważ brakuje jej tylko publiczności.
— Alexis Paques

12

Wygląda na to, że za bardzo się starasz. shared_ptrjest tani w kopiowaniu; to jeden z jego celów. Przekazywanie ich przez odniesienie tak naprawdę niewiele daje. Jeśli nie chcesz udostępniać, przekaż nieprzetworzony wskaźnik.

To powiedziawszy, są na to dwa sposoby, które przychodzą mi do głowy:

foo(shared_ptr<Base>(bar));
foo(static_pointer_cast<Base>(bar));

9
Nie, nie są tanie w kopiowaniu, należy je przekazywać jako odniesienie, gdy tylko jest to możliwe.
— Seth Carnegie

6
@SethCarnegie - czy Herb profilował swój kod, aby sprawdzić, czy przekazywanie wartości było wąskim gardłem?
— Pete Becker

25
@SethCarnegie - to nie odpowiada na zadane przeze mnie pytanie. I co jest warte, napisałem shared_ptrimplementację, którą dostarcza Microsoft.
— Pete Becker

6
@SethCarnegie - masz odwrotną heurystykę. Zasadniczo optymalizacji układów nie należy wykonywać, chyba że możesz wykazać, że są potrzebne.
— Pete Becker

21
To tylko „przedwczesna” optymalizacja, jeśli trzeba nad tym popracować. Nie widzę problemu w przyjmowaniu skutecznych idiomów zamiast nieefektywnych, niezależnie od tego, czy robi to różnicę w określonym kontekście, czy nie.
— Mark Ransom

11

Sprawdź również, czy #includeplik nagłówkowy zawierający pełną deklarację klasy pochodnej znajduje się w pliku źródłowym.

Miałem ten problem. Nie std::shared<derived>chciał rzucać std::shared<base>. Zadeklarowałem obie klasy, aby móc przechowywać do nich wskaźniki, ale ponieważ nie miałem #includekompilatora, nie mogłem zobaczyć, że jedna klasa pochodzi od drugiej.


1
Wow, nie spodziewałem się tego, ale to naprawiło to dla mnie. Byłem bardzo ostrożny i włączałem pliki nagłówkowe tylko tam, gdzie ich potrzebowałem, więc niektóre z nich były tylko w plikach źródłowych i dalej deklarując je w nagłówkach, jak powiedziałeś.
— jigglypuff

Głupi kompilator jest głupi. To był mój problem. Dziękuję Ci!
— Tanveer Badar
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.