Zwykło się na ogół zaleca najlepszych praktyk 1 do użytku przejściu przez const ref dla wszystkich typów , z wyjątkiem typów (wbudowane char
, int
, double
itp), na iteratory i obiektów funkcyjnych (lambda, zajęcia wynikające z std::*_function
).
Było to szczególnie prawdziwe przed istnieniem semantyki ruchów . Powód jest prosty: jeśli przeszedłeś przez wartość, musisz wykonać kopię obiektu i, z wyjątkiem bardzo małych obiektów, zawsze jest to droższe niż przekazanie referencji.
Dzięki C ++ 11 uzyskaliśmy semantykę ruchów . W skrócie, semantyka przenoszenia pozwala, aby w niektórych przypadkach obiekt mógł być przekazywany „przez wartość” bez kopiowania go. W szczególności dzieje się tak, gdy obiekt, który mijasz, jest wartością .
Samo poruszanie się obiektu jest nadal co najmniej tak samo drogie, jak przejście przez odniesienie. Jednak w wielu przypadkach funkcja i tak wewnętrznie kopiuje obiekt - tzn. Przejmuje własność argumentu. 2)
W takich sytuacjach mamy następującą (uproszczoną) kompromis:
- Możemy przekazać obiekt przez odniesienie, a następnie skopiować go wewnętrznie.
- Możemy przekazać obiekt według wartości.
„Przekaż przez wartość” nadal powoduje skopiowanie obiektu, chyba że obiekt jest wartością zmienną. W przypadku wartości można zamiast tego przesuwać obiekt, aby drugi przypadek przestał nagle „kopiować, następnie przesuwać”, ale „przesuwać, a następnie (potencjalnie) przesuwać ponownie”.
W przypadku dużych obiektów, które implementują odpowiednie konstruktory ruchów (takie jak wektory, łańcuchy…), drugi przypadek jest wtedy znacznie wydajniejszy niż pierwszy. Dlatego zaleca się użycie parametru pass by value, jeśli funkcja przejmuje własność argumentu i jeśli typ obiektu obsługuje wydajne przenoszenie .
Uwaga historyczna:
W rzeczywistości każdy współczesny kompilator powinien być w stanie dowiedzieć się, kiedy przekazywanie wartości jest drogie, i jeśli to możliwe, pośrednio przekonwertować wywołanie, aby używało stałej referencji.
W teorii. W praktyce kompilatory nie zawsze mogą to zmienić bez zerwania binarnego interfejsu funkcji. W niektórych szczególnych przypadkach (gdy funkcja jest wbudowana) kopia zostanie pominięta, jeśli kompilator może stwierdzić, że oryginalny obiekt nie zostanie zmieniony poprzez działania w funkcji.
Ale generalnie kompilator nie może tego ustalić, a pojawienie się semantyki ruchu w C ++ sprawiło, że ta optymalizacja jest mniej istotna.
1 Np. W Scott Meyers, Effective C ++ .
2 Jest to szczególnie często prawdziwe w przypadku konstruktorów obiektów, które mogą przyjmować argumenty i przechowywać je wewnętrznie, aby były częścią stanu zbudowanego obiektu.