Czy w C++ lepiej przekazać wartość czy przekazać stałą referencję?

Czy lepiej w C++ przekazać wartość czy przekazać stałą referencję?

Zastanawiam się, która jest lepsza praktyka. Zdaję sobie sprawę, że przejście przez stałe odniesienie powinno zapewnić lepszą wydajność w programie, ponieważ nie robisz kopii zmiennej.

Author: Kevin, 2008-11-07

10 answers

To była ogólnie zalecana najlepsza praktyka1 Aby użyć pass by const ref dla wszystkich typów , z wyjątkiem typów wbudowanych (char, int, double, itd.), dla iteratorów i dla obiektów funkcyjnych (lambda, klasy wywodzące się z std::*_function).

Było to szczególnie prawdziwe przed istnieniem semantyki ruchu . Powód jest prosty: jeśli przekazałeś wartość, trzeba było wykonać kopię obiektu i, z wyjątkiem bardzo małych obiektów, jest to zawsze droższe niż podanie referencji.

W C++11 zyskaliśmy semantyka ruchu. W skrócie, semantyka move pozwala, że w niektórych przypadkach obiekt może być przekazywany "przez wartość" bez kopiowania go. W szczególności ma to miejsce, gdy obiekt, który mijasz, jest rvalue.

Sam w sobie ruch obiektu jest co najmniej tak samo kosztowny, jak przejście przez odniesienie. Jednak w wielu przypadkach funkcja i tak wewnętrznie skopiuje obiekt - tzn. przejmie własność argumentu.2

W takich sytuacjach mamy następujący (uproszczony) kompromis:

  1. możemy przekazać obiekt przez odniesienie, a następnie skopiować go wewnętrznie.
  2. możemy przekazać obiekt według wartości.

"Pass by value" nadal powoduje skopiowanie obiektu, chyba że obiekt jest wartością r. W przypadku wartości R, obiekt może być przeniesiony, tak że drugi przypadek nagle nie jest już "Kopiuj, następnie przenieś", ale " przenieś, następnie (potencjalnie) przenieść ponownie".

Dla dużych obiektów, które implementują odpowiednie konstruktory ruchu (takie jak wektory, ciągi ...), drugi przypadek jest wtedy znacznie bardziej efektywny niż pierwszy. Dlatego zaleca się, aby używać wartości pass by, Jeśli funkcja przejmuje własność argumentu i jeśli typ obiektu obsługuje efektywne przenoszenie.


Uwaga historyczna:

W rzeczywistości każdy nowoczesny kompilator powinien być w stanie zorientować się, kiedy przekazywanie przez wartość jest kosztowne, a w miarę możliwości konwersja wywołania na const ref.

teoretycznie. w praktyce Kompilatory nie zawsze mogą to zmienić bez uszkodzenia interfejsu binarnego funkcji. W niektórych szczególnych przypadkach (gdy funkcja jest inlined) kopia będzie faktycznie elided, jeśli kompilator może dowiedzieć się, że oryginalny obiekt nie zostanie zmieniony poprzez działania w funkcji.

Ale ogólnie kompilator nie może tego określić, a pojawienie się semantyki move w C++ sprawił, że ta optymalizacja była znacznie mniej istotna.


1 np. w Scott Meyers, efektywny C++.

2 jest to szczególnie często prawdziwe w przypadku konstruktorów obiektów, które mogą pobierać argumenty i przechowywać je wewnętrznie jako część stanu konstruowanego obiektu.

 175
Author: Konrad Rudolph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:33

Edit: Nowy Artykuł Dave Abrahams na cpp-następny:

Chcesz prędkości? Podaj wartość.


Przekazywanie wartości dla struktur, w których kopiowanie jest tanie, ma dodatkową zaletę, że kompilator może założyć, że obiekty nie są aliasami (nie są tymi samymi obiektami). Używając pass-by-reference kompilator nie może zakładać, że zawsze. Prosty przykład:

foo * f;

void bar(foo g) {
    g.i = 10;
    f->i = 2;
    g.i += 5;
}

Kompilator może zoptymalizować go do

g.i = 15;
f->i = 2;

Ponieważ wie, że f I g nie dzielą to samo miejsce. jeśli g było referencją (foo &), kompilator nie mógł tego zakładać. ponieważ g. i może być aliased przez f- > i I musi mieć wartość 7. więc kompilator musiałby ponownie pobrać nową wartość g. i z pamięci.

Aby uzyskać bardziej praktyczne zasady, poniżej znajduje się dobry zbiór zasad znalezionych w konstruktory ruchu artykuł (wysoce zalecana lektura).

  • jeśli funkcja zamierza zmienić argument jako efekt uboczny, weź go przez odniesienie non-const.
  • jeśli funkcja nie zmienia swojego argumentu, a argument jest typu prymitywnego, weź go według wartości.
  • w przeciwnym razie weź to przez const reference, z wyjątkiem następujących przypadków
    • jeśli funkcja i tak będzie musiała wykonać kopię referencji const, weź ją według wartości.

"prymitywny" powyżej oznacza w zasadzie małe typy danych, które mają kilka bajtów i nie są polimorficzne (Iteratory, obiekty funkcyjne itp...) lub drogie w kopiowaniu. W ta gazeta, jest jeszcze jedna zasada. Chodzi o to, że czasami chce się zrobić kopię (w przypadku, gdy argument nie może być zmodyfikowany), a czasami nie chce (w przypadku, gdy chce się użyć samego argumentu w funkcji, jeśli argument i tak był tymczasowy, na przykład). Artykuł szczegółowo wyjaśnia, w jaki sposób można to zrobić. W C++1x ta technika może być używana natywnie z obsługą języka. Do tego czasu przestrzegałbym powyższych zasad.

Przykłady: aby utworzyć łańcuch wielkich liter i zwracając wersję z dużymi literami, należy zawsze przekazać wartość: trzeba i tak wziąć jej kopię (nie można było zmienić bezpośrednio referencji const) - więc lepiej uczynić ją tak przejrzystą, jak to możliwe dla wywołującego i wykonać tę kopię wcześniej, aby wywołujący mógł zoptymalizować tak bardzo, jak to możliwe - jak szczegółowo opisano w tym artykule: {]}

my::string uppercase(my::string s) { /* change s and return it */ }

Jeśli jednak nie musisz zmieniać parametru, weź go przez odniesienie do const:

bool all_uppercase(my::string const& s) { 
    /* check to see whether any character is uppercase */
}

Jeśli jednak celem parametru jest napisz coś do argumentu, a następnie przekaż go przez referencję non-const

bool try_parse(T text, my::string &out) {
    /* try to parse, write result into out */
}
 90
Author: Johannes Schaub - litb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-09 21:32:09

Zależy od typu. Dodajesz mały narzut z konieczności dokonania odniesienia i dereferencji. Dla typów o rozmiarze równym lub mniejszym niż wskaźniki, które używają domyślnej kopii ctor, prawdopodobnie szybciej byłoby przekazać wartość.

 12
Author: Lou Franco,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-06 21:47:18

Jak już wspomniano, zależy to od typu. W przypadku wbudowanych typów danych najlepiej jest przekazać wartość. Nawet niektóre bardzo małe struktury, takie jak para int mogą działać lepiej, przechodząc przez wartość.

Oto przykład, załóżmy, że masz wartość całkowitą i chcesz przekazać ją do innej procedury. Jeśli ta wartość została zoptymalizowana do przechowywania w Rejestrze, to jeśli chcesz przekazać ją jako referencję, najpierw musi być zapisana w pamięci, a następnie wskaźnik do tej pamięci umieszczony na stos do wykonania połączenia. Jeśli był przekazywany przez wartość, wszystko, co jest wymagane, to rejestr wypchnięty na stos. (Szczegóły są nieco bardziej skomplikowane niż w przypadku różnych systemów wywołujących i procesorów).

Jeśli zajmujesz się programowaniem szablonów, Zwykle jesteś zmuszony do podania const ref, ponieważ nie wiesz, jakie typy są przekazywane. Kary za przekazanie czegoś złego przez wartość są znacznie gorsze niż kary za przekazanie wbudowanego typu przez const Nr ref.

 8
Author: Torlack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-08 17:36:17

Wygląda na to, że masz swoją odpowiedź. Przekazywanie wartości jest kosztowne, ale daje kopię do pracy, jeśli jej potrzebujesz.

 5
Author: GeekyMonkey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-06 21:44:24

Z reguły przechodzenie przez referencję const jest lepsze. Ale jeśli chcesz zmodyfikować argument funkcji lokalnie, powinieneś lepiej użyć wartości passing by. W przypadku niektórych podstawowych typów wydajność w ogóle taka sama zarówno dla przekazywania przez wartość, jak i przez odniesienie. W rzeczywistości Referencja wewnętrznie reprezentowana przez wskaźnik, dlatego można oczekiwać na przykład, że dla wskaźnika oba przejścia są takie same pod względem wydajności, lub nawet przekazywanie przez wartość może być szybsze z powodu niepotrzebnej dereferencji.

 4
Author: sergtk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-06 21:50:21

To jest to, co zwykle pracuję przy projektowaniu interfejsu funkcji nie-szablonowej:

  1. Przekazać wartość, jeśli funkcja nie chce modyfikować parametru i wartość jest tania w kopiowaniu (int, double, float, char, bool, itp... Zauważ, że std:: string, std:: vector i reszta kontenerów w bibliotece standardowej nie są)

  2. Przechodzić przez wskaźnik const, jeśli wartość jest kosztowna do skopiowania, a funkcja robi nie chcesz modyfikować wskazywanej wartości a NULL jest wartością, którą obsługuje funkcja.

  3. Przechodzić przez wskaźnik non-const, jeśli wartość jest kosztowna do skopiowania, a funkcja chce zmodyfikować wskazywaną wartość, a NULL jest wartością obsługiwaną przez funkcję.

  4. Przekazuje referencję const, gdy wartość jest kosztowna do skopiowania, a funkcja nie chce modyfikować wartości, o której mowa, a NULL nie byłaby prawidłową wartością, gdyby zamiast niej użyto wskaźnika.

  5. Przekazać przez odniesienie non-const, gdy wartość jest kosztowna w kopiowaniu i funkcja chce zmodyfikować wartość, o której mowa, a NULL nie byłaby poprawną wartością, gdyby zamiast niej użyto wskaźnika.

 4
Author: Martin G,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-11 07:02:41

Jako zasada, wartość dla typów nieklasowych i const reference dla klas. Jeśli klasa jest naprawdę mała, prawdopodobnie lepiej przekazać wartość, ale różnica jest minimalna. To, czego naprawdę chcesz uniknąć, to przekazywanie jakiejś gigantycznej klasy po wartości i powielanie jej wszystkich - to zrobi ogromną różnicę, jeśli przechodzisz, powiedzmy, std::vector z kilkoma elementami w nim.

 1
Author: Peter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-06 22:04:00

Podaj wartość dla małych typów.

Przekazywanie referencji const dla typów dużych (definicja big może się różnić w zależności od maszyn), ale w C++11 przekazywanie wartości, jeśli chcesz zużyć dane, ponieważ możesz wykorzystać semantykę move. Na przykład:

class Person {
 public:
  Person(std::string name) : name_(std::move(name)) {}
 private:
  std::string name_;
};

Teraz kod wywoławczy zrobi:

Person p(std::string("Albert"));

I tylko jeden obiekt zostałby utworzony i przeniesiony bezpośrednio do członka name_ w klasie Person. Jeśli przejdziesz obok const reference, trzeba będzie wykonać kopię, aby umieścić ją w name_.

 1
Author: Germán Diago,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-29 13:48:09

Prosta różnica: - w funkcji mamy parametr wejściowy i wyjściowy, więc jeśli twój parametr wejściowy i wyjściowy jest taki sam, Użyj wywołania przez odniesienie, inaczej jeśli parametr wejściowy i wyjściowy są różne, lepiej użyć wywołania przez wartość .

Przykład void amount(int account , int deposit , int total )

Parametr wejściowy: konto, wpłata parametr wyjściowy: total

Input i out to różne use call by vaule

  1. void amount(int total , int deposit )

Input total deposit output total

 -5
Author: Dhirendra Sengar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-05 09:37:55