Gdy funkcja przyjmuje wartość shared_ptr
(z boost lub C ++ 11 STL), przekazujesz ją:
według stałej referencji:
void foo(const shared_ptr<T>& p)
lub według wartości
void foo(shared_ptr<T> p)
:?
Wolałbym pierwszą metodę, ponieważ podejrzewam, że byłaby szybsza. Ale czy to naprawdę jest tego warte, czy są jakieś dodatkowe problemy?
Czy możesz podać powody swojego wyboru lub, jeśli tak, dlaczego uważasz, że to nie ma znaczenia.
c++
c++11
boost
shared-ptr
Danvil
źródło
źródło
shared_ptr
i mogę to zmienić, jeśli chcę.”, Podczas gdy wersja wartościowa mówi „Skopiuję twójshared_ptr
, więc dopóki mogę to zmienić, nigdy się nie dowiesz. ) Parametr const-reference jest prawdziwym rozwiązaniem, które mówi: „Zamierzam trochę pseudonimshared_ptr
i obiecuję, że go nie zmienię.” (Który jest niezwykle podobny do semantyki według wartości!)shared_ptr
klasy. Czy robisz to przez const-refs?Odpowiedzi:
To pytanie zostało omówione i udzielone przez Scott, Andrei i Herb podczas sesji Ask Us Anything w C ++ i Beyond 2011 . Obejrzyj od 4:34 na temat
shared_ptr
wydajności i poprawności .Krótko mówiąc, nie ma powodu, by przekazywać wartość, chyba że celem jest współwłasność własności obiektu (np. Między różnymi strukturami danych lub między różnymi wątkami).
O ile nie możesz go zoptymalizować w ruchu, jak wyjaśnił Scott Meyers w powyższym filmie dyskusyjnym, ale jest to związane z faktyczną wersją C ++, której możesz użyć.
Główna aktualizacja tej dyskusji nastąpiła podczas interaktywnego panelu konferencji GoingNative 2012 : Zapytaj nas o wszystko! co warto obejrzeć, zwłaszcza od 22:50 .
źródło
Value*
jest krótki i czytelny, ale zły, więc teraz mój kod jest pełnyconst shared_ptr<Value>&
i jest znacznie mniej czytelny i po prostu ... mniej uporządkowany. To, co kiedyś było,void Function(Value* v1, Value* v2, Value* v3)
jest terazvoid Function(const shared_ptr<Value>& v1, const shared_ptr<Value>& v2, const shared_ptr<Value>& v3)
, a ludzie się z tym zgadzają?class Value {...}; using ValuePtr = std::shared_ptr<Value>;
Twoja funkcja staje się prostsza:void Function(const ValuePtr& v1, const ValuePtr& v2, const ValuePtr& v3)
i uzyskujesz maksymalną wydajność. Dlatego używasz C ++, prawda? :)Oto ujęcie Herb Suttera
źródło
Osobiście użyłbym
const
referencji. Nie ma potrzeby zwiększania liczby referencyjnej tylko po to, aby ją ponownie zmniejszyć ze względu na wywołanie funkcji.źródło
shared_ptr
działania, jedynym możliwym minusem nieprzekazania odniesienia jest niewielka utrata wydajności. Są tu dwie przyczyny. a) funkcja aliasingu wskaźnika oznacza, że dane są warte wskaźników, a licznik (może 2 dla słabych referencji) jest kopiowany, więc kopiowanie danych jest nieco droższe. b) zliczanie referencji atomowych jest nieco wolniejsze niż zwykły stary kod inkrementacji / dekrementacji, ale jest potrzebne, aby zapewnić bezpieczeństwo wątków. Poza tym obie metody są takie same dla większości celów i celów.Przekaż przez
const
odniesienie, jest szybszy. Jeśli chcesz go przechowywać, powiedzmy w jakimś pojemniku, ref. liczba zostanie automatycznie zwiększona przez operację kopiowania.źródło
Uruchomiłem poniższy kod, raz z
foo
przyjmowaniem wartości „shared_ptr
by by”const&
i „raz po raz” zfoo
przyjmowaniemshared_ptr
wartości według.Używając wersji VS2015, x86, na moim czterordzeniowym procesorze Intel Core 2 (2,4 GHz)
Wersja kopiowania według wartości była wolniejsza o rząd wielkości.
Jeśli wywołujesz funkcję synchronicznie z bieżącego wątku, wybierz
const&
wersję.źródło
foo()
funkcja nie powinna nawet w ogóle przyjmować wspólnego wskaźnika, ponieważ nie korzysta z tego obiektu: powinna akceptowaćint&
i robićp = ++x;
, wywołującfoo(*p);
zmain()
. Funkcja akceptuje inteligentny obiekt wskaźnika, gdy musi coś z nim zrobić, i przez większość czasu musisz go przenieść (std::move()
) w inne miejsce, więc parametr według wartości nie kosztuje.Od C ++ 11 powinieneś brać to pod względem wartości ponad const i częściej niż myślisz.
Jeśli bierzesz std :: shared_ptr (zamiast bazowego typu T), robisz to, ponieważ chcesz coś z tym zrobić.
Jeśli chcesz go gdzieś skopiować , sensowniej jest zabrać go za pomocą kopiowania i przenieść std :: wewnętrznie, zamiast zabierać to przez const &, a następnie kopiowanie. Jest tak, ponieważ pozwalasz wywołującemu opcję z kolei std :: move shared_ptr podczas wywoływania twojej funkcji, oszczędzając w ten sposób sobie zestaw operacji zwiększania i zmniejszania. Albo nie. Oznacza to, że wywołujący funkcję może zdecydować, czy potrzebuje std :: shared_ptr po wywołaniu funkcji, w zależności od tego, czy się poruszy, czy nie. Nie jest to możliwe, jeśli przejdziesz przez const &, a zatem najlepiej jest przyjąć wartość.
Oczywiście, jeśli osoba wywołująca potrzebuje dłużej swojej shared_ptr (więc nie może std :: move it) i nie chcesz tworzyć zwykłej kopii w funkcji (powiedz, że chcesz słaby wskaźnik, lub tylko czasami chcesz aby go skopiować, w zależności od niektórych warunków), wtedy const i może być nadal preferowane.
Na przykład powinieneś to zrobić
nad
Ponieważ w takim przypadku zawsze tworzysz kopię wewnętrznie
źródło
Nie znając kosztu czasu operacji kopiowania shared_copy, w której występuje przyrost i spadek atomowy, cierpiałem z powodu znacznie większego problemu z użyciem procesora. Nigdy nie spodziewałem się, że przyrost atomowy i dekrementacja mogą kosztować tyle.
Po moim teście, przyrost i spadek atomowy int32 zajmuje 2 lub 40 razy niż wzrost i spadek nieatomowy. Mam go na Core i7 3GHz z Windows 8.1. Pierwszy wynik pojawia się, gdy nie dochodzi do rywalizacji, drugi, gdy występuje duża możliwość rywalizacji. Pamiętam, że operacje atomowe są w końcu blokadą sprzętową. Zamek jest zamkiem. Zła wydajność, gdy występuje rywalizacja.
Doświadczając tego, zawsze używam byref (const shared_ptr &) niż byval (shared_ptr).
źródło
Był ostatni post na blogu: https://medium.com/@vgasparyan1995/pass-by-value-vs-pass-by-reference-to-const-c-f8944171e3ce
Odpowiedź na to brzmi: nigdy (prawie) nigdy nie omijaj
const shared_ptr<T>&
.Zamiast tego po prostu przekaż klasę bazową.
Zasadniczo jedynymi rozsądnymi typami parametrów są:
shared_ptr<T>
- Zmień i przejmij własnośćshared_ptr<const T>
- Nie modyfikuj, przejmuj na własnośćT&
- Zmień, bez prawa własnościconst T&
- Nie modyfikuj, brak prawa własnościT
- Nie modyfikuj, nie posiadaj, tanie do skopiowaniaJak zauważył @accel w https://stackoverflow.com/a/26197326/1930508 rada Herb Sutter jest następująca:
Ale w ilu przypadkach nie jesteś pewien? To rzadka sytuacja
źródło
Znany jest problem polegający na tym, że przekazywanie wartości parametru shared_ptr wiąże się z pewnymi kosztami i należy go w miarę możliwości unikać.
Koszt przejścia przez shared_ptr
Przeważnie wystarczyłoby dzielenie share_ptr przez referencję, a jeszcze lepiej przez const referen.
Podstawowa wytyczna cpp ma określoną zasadę przekazywania komendy shared_ptr
R.34: Weź parametr shared_ptr, aby wyrazić, że funkcja jest właścicielem części
Przykładem, gdy przekazanie parametru shared_ptr przez wartość jest naprawdę konieczne, jest to, gdy obiekt wywołujący przekazuje obiekt współdzielony do asynchronicznego odbiorcy - tzn. Dzwoniący wykracza poza zakres, zanim odbiorca zakończy swoje zadanie. Odbiorca musi „przedłużyć” żywotność współdzielonego obiektu, biorąc wartość share_ptr według wartości. W takim przypadku przekazanie odwołania do shared_ptr nie zadziała.
To samo dotyczy przekazywania współdzielonego obiektu do wątku roboczego.
źródło
shared_ptr nie jest wystarczająco duży, ani jego konstruktor \ destruktor nie wykonuje wystarczającej pracy, aby z kopii było wystarczające obciążenie, aby dbać o przekazywanie przez odniesienie w porównaniu do wydajności kopiowania przez kopię.
źródło
shared_ptr<int>
wartości przez zajmuje 100 instrukcji x86 (w tym drogichlock
instrukcji ed, aby atomowo zwiększyć / zmniejszyć liczbę referencji). Przekazywanie przez stałą referencję jest takie samo, jak przekazywanie wskaźnika do czegokolwiek (w tym przykładzie w eksploratorze kompilatora Godbolt optymalizacja wywołania ogona zamienia to w zwykły plik jmp zamiast wywołania: godbolt.org/g/TazMBU ).