W tradycyjnym C ++ przekazywanie wartości do funkcji i metod jest powolne w przypadku dużych obiektów i generalnie jest źle widziane. Zamiast tego programiści C ++ mają tendencję do przekazywania referencji, co jest szybsze, ale wprowadza różnego rodzaju skomplikowane pytania dotyczące własności, a zwłaszcza zarządzania pamięcią (w przypadku, gdy obiekt jest alokowany na stosie)
Teraz w C ++ 11 mamy referencje Rvalue i konstruktory przenoszenia, co oznacza, że można zaimplementować duży obiekt (taki jak std::vector
), który jest tani do przekazywania wartości do i z funkcji.
Czy to oznacza, że wartością domyślną powinno być przekazywanie wartości dla wystąpień typów, takich jak std::vector
i std::string
? A co z obiektami niestandardowymi? Jaka jest nowa najlepsza praktyka?
źródło
pass by reference ... which introduces all sorts of complicated questions around ownership and especially around memory management (in the event that the object is heap-allocated)
. Nie rozumiem, jak skomplikowane lub problematyczne jest posiadanie? Może coś mi umknęło?const std::string&
kopię, a nie kopię. Wtedy pierwszy wątek wyszedł ...Odpowiedzi:
Jest to rozsądne ustawienie domyślne, jeśli chcesz wykonać kopię wewnątrz ciała. Oto, za czym opowiada się Dave Abrahams :
W kodzie oznacza to, że nie rób tego:
ale zrób to:
który ma tę zaletę, że dzwoniący może używać w następujący
foo
sposób:i tylko minimalna praca jest wykonywana. Potrzebujesz dwóch przeciążeń, aby zrobić to samo z odwołaniami,
void foo(T const&);
ivoid foo(T&&);
.Mając to na uwadze, napisałem teraz moich cenionych konstruktorów jako takich:
W przeciwnym razie przekazanie przez odniesienie do
const
nadal jest rozsądne.źródło
SomeProperty p;
for (auto x: vec) { x.foo(p); }
na przykład nie pasuje. Ponadto, ruch konstruktorów ma swój koszt (im większy obiekt, tym wyższy koszt), podczas gdyconst&
są zasadniczo bezpłatne.std::vector
zawierającego milion elementów kosztuje tyle samo, co przeniesienie elementu zawierającego pięć elementów, ponieważ przesuwany jest tylko wskaźnik do tablicy na stercie, a nie każdy obiekt w wektorze. Więc to nie jest tak duży problem.std::move
wszędzie ...const&
, które kilka razy mnie przewróciło.void foo(const T&); int main() { S s; foo(s); }
. Może się to skompilować, nawet jeśli typy są różne, jeśli istnieje konstruktor T, który przyjmuje S jako argument. Może to być powolne, ponieważ może być konstruowany duży obiekt T. Możesz pomyśleć, że przekazujesz referencje bez kopiowania, ale możesz tak. Zobacz tę odpowiedź na pytanie, które zadałem więcej. Zasadniczo&
zwykle wiąże się tylko z lvalues, ale jest wyjątek dlarvalue
. Są alternatywy.W prawie wszystkich przypadkach Twoja semantyka powinna wyglądać następująco:
Wszystkie inne podpisy powinny być używane oszczędnie i z dobrym uzasadnieniem. Kompilator będzie teraz prawie zawsze rozwiązywać je w najbardziej efektywny sposób. Możesz po prostu kontynuować pisanie kodu!
źródło
foo(bar& x) { x.a = 3; }
Jest o wiele bardziej niezawodne (i czytelne!) Niżfoo(bar* x) {if (!x) throw std::invalid_argument("x"); x->a = 3;
ref
słowo kluczowe w C #).is shared_ptr intended to never be null? Much as (I think) unique_ptr is?
Oba te założenia są niepoprawne.unique_ptr
ishared_ptr
może zawierać wartości null /nullptr
. Jeśli nie chcesz martwić się o wartości null, powinieneś używać odwołań, ponieważ nigdy nie mogą być puste. Nie będziesz też musiał pisać->
, co Cię denerwuje :)Przekaż parametry według wartości, jeśli w treści funkcji potrzebujesz kopii obiektu lub musisz tylko przenieść obiekt. Pomiń,
const&
jeśli potrzebujesz tylko niemutującego dostępu do obiektu.Przykład kopii obiektu:
Przykład przeniesienia obiektu:
Przykład dostępu bez mutacji:
Dla uzasadnienia zobacz te posty na blogu autorstwa Dave'a Abrahamsa i Xiang Fan .
źródło
Podpis funkcji powinien odzwierciedlać jej przeznaczenie. Czytelność jest ważna, także dla optymalizatora.
Jest to najlepszy warunek wstępny dla optymalizatora do stworzenia najszybszego kodu - przynajmniej w teorii, a jeśli nie w rzeczywistości, to za kilka lat.
Zagadnienia dotyczące wydajności są bardzo często przeceniane w kontekście przekazywania parametrów. Przykładem jest perfekcyjna spedycja. Funkcje takie jak
emplace_back
są przeważnie bardzo krótkie i tak czy inaczej wbudowane.źródło