int vs const int&
Zauważyłem, że zwykle używam stałych referencji jako wartości zwracanych lub argumentów. Myślę, że powodem jest to, że działa prawie tak samo jak użycie braku odniesienia w kodzie. Ale zdecydowanie zajmuje więcej miejsca, a deklaracje funkcji stają się dłuższe. Jestem w porządku z takim kodem, ale myślę, że niektórzy uważają, że to zły styl programowania.
Co o tym myślisz? Czy warto pisać const int&nad int? Myślę, że i tak jest zoptymalizowany przez kompilator, więc może po prostu marnuję czas na kodowanie, a?
5 answers
W C++ Bardzo często jest to, co uważam za anty-wzorzec, który używa const T&
jako inteligentnego sposobu mówienia T
podczas radzenia sobie z parametrami. Jednak wartość i odniesienie (bez względu na to, czy const czy nie) to dwie zupełnie różne rzeczy i zawsze i ślepo używanie odniesień zamiast wartości może prowadzić do subtelnych błędów.
Powodem jest to, że mając do czynienia z odniesieniami należy wziąć pod uwagę dwie kwestie, które nie występują z wartościami: lifetime i aliasing.
Jako przykład jednym z miejsc, gdzie ten anty-wzorzec jest stosowany jest sama biblioteka standardowa, gdzie std::vector<T>::push_back
przyjmuje jako parametr a const T&
zamiast wartości i może to być na przykład w kodzie jak:
std::vector<T> v;
...
if (v.size())
v.push_back(v[0]); // Add first element also as last element
Ten kod jest tykającą bombą, ponieważ std::vector::push_back
chce referencji const, ale wykonanie push_back może wymagać realokacji, a jeśli tak się stanie, oznacza to, że po realokacji otrzymane referencje nie będą już ważne ( lifetime issue) I wchodzisz w sferę nieokreślonego zachowania.
Problemy z aliasingiem są również źródłem subtelnych problemów, jeśli zamiast wartości używane są odwołania const. Zostałem ugryziony np. przez taki kod:
struct P2d
{
double x, y;
P2d(double x, double y) : x(x), y(y) {}
P2d& operator+=(const P2d& p) { x+=p.x; y+=p.y; return *this; }
P2d& operator-=(const P2d& p) { x-=p.x; y-=p.y; return *this; }
};
struct Rect
{
P2d tl, br;
Rect(const P2d& tl, const P2d& br) : tl(tl), bt(br) {}
Rect& operator+=(const P2d& p) { tl+=p; br+=p; return *this; }
Rect& operator-=(const P2d& p) { tl-=p; br-=p; return *this; }
};
Kod wydaje się na pierwszy rzut oka dość bezpieczny, P2d
jest dwuwymiarowym punktem, Rect
jest prostokątem, a dodawanie / odejmowanie punktu oznacza tłumaczenie prostokąta.
Jeśli jednak przetłumaczyć prostokąt z powrotem w źródle ty write myrect -= myrect.tl;
kod nie będzie działał, ponieważ operator tłumaczenia został zdefiniowany akceptując odniesienie ,które (w tym przypadku) odnosi się do członka tej samej instancji.
Oznacza to, że po zaktualizowaniu topleft z tl -= p;
topleft będzie (0, 0)
tak jak powinien, ale również p
stanie się w tym samym czasie (0, 0)
ponieważ p
jest tylko odniesieniem do lewego górnego członu, więc aktualizacja prawego dolnego rogu nie będzie działać, ponieważ przetłumaczy go przez (0, 0)
stąd w zasadzie nic.
Proszę nie dać się nabrać na myślenie, że Referencja const jest jak wartość ze względu na słowo const
. To słowo istnieje tylko po to, aby spowodować błędy kompilacji, jeśli spróbujesz zmienić obiekt odniesienia używając tego odniesienia , ale nie oznacza to, że obiekt odniesienia jest stały. W szczególności obiekt, do którego odwołuje się const ref, może się zmieniać (np. z powodu aliasingu) i może nawet przestać istnieć, gdy go używasz ( lifetime issue).
W const T&
Słowo const wyraża właściwość referencji, a nie obiektu odniesienia: jest to właściwość, która uniemożliwia użycie jej do zmiany obiektu. Prawdopodobnie readonly byłaby lepsza nazwa, ponieważ const mA IMO psychologiczny efekt popychania idei, że obiekt będzie stały podczas korzystania z odniesienia.
Można oczywiście uzyskać imponujące przyspiesza poprzez użycie referencji zamiast kopiowania wartości, szczególnie dla dużych klas. Ale zawsze powinieneś myśleć o aliasingu i problemach z czasem życia, gdy używasz referencji, ponieważ pod osłoną są tylko wskaźnikami do innych danych. W przypadku "natywnych" typów danych (int, doubles, pointers) odwołania będą jednak w rzeczywistości wolniejsze niż wartości i nie ma nic do zyskania w ich użyciu zamiast wartości.
Również odniesienie const będzie zawsze oznaczać problemy dla optymalizatora jako kompilator zmuszony jest popadać w paranoję i za każdym razem, gdy jakiś nieznany kod jest wykonywany, musi zakładać, że wszystkie odwołujące się obiekty mogą mieć teraz inną wartość (const
dla odniesienia nie znaczy absolutnie nic dla optymalizatora; to słowo jest tam tylko po to, aby pomóc programistom - osobiście nie jestem pewien, czy to taka duża pomoc, ale to już inna historia).
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
2018-01-18 08:01:48
Jak mówi Oli, zwracanie const T&
w przeciwieństwie do {[1] } to zupełnie inne rzeczy i może się zepsuć w pewnych sytuacjach (jak w jego przykładzie).
Biorąc const T&
w przeciwieństwie do zwykłego T
jako argument jest mniej prawdopodobne, aby złamać rzeczy, ale nadal mają kilka istotnych różnic.
- Użycie
T
zamiastconst T&
wymaga, abyT
było kopiowalne. - wzięcie
T
wywoła Konstruktor kopiujący, który może być drogi (a także Destruktor na funkcja exit). - Użycie
T
pozwala na modyfikację parametru jako zmiennej lokalnej (może być szybsze niż ręczne kopiowanie). - przyjmowanie
const T&
może być wolniejsze ze względu na niewspółosiowość czasową i koszt indirection.
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
2011-01-16 13:46:28
int &
i int
nie są wymienne! W szczególności, jeśli zwrócisz odniesienie do lokalnej zmiennej stosu, zachowanie jest niezdefiniowane, np.:
int &func()
{
int x = 42;
return x;
}
You can return a reference to something that won ' t be destroyed at the end of the function (np. a static, or a class member). Więc to jest ważne:
int &func()
{
static int x = 42;
return x;
}
I do świata zewnętrznego, ma taki sam efekt jak zwrócenie int
bezpośrednio (tyle, że teraz możesz go zmodyfikować, dlatego widzisz const int &
a Nr serii).
Zaletą referencji jest to, że nie jest Wymagana kopia, co jest ważne, jeśli masz do czynienia z dużymi obiektami klasy. Jednak w wielu przypadkach kompilator może to zoptymalizować; patrz np. http://en.wikipedia.org/wiki/Return_value_optimization .
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
2011-01-16 13:35:48
Jeśli wywołanie i wywołanie są zdefiniowane w osobnych jednostkach kompilacji, to kompilator nie może zoptymalizować odwołania. Na przykład skompilowałem następujący kod:
#include <ctime>
#include <iostream>
int test1(int i);
int test2(const int& i);
int main() {
int i = std::time(0);
int j = test1(i);
int k = test2(i);
std::cout << j + k << std::endl;
}
Z G++ na 64-bitowym Linuksie na poziomie optymalizacji 3. Pierwsze wywołanie nie wymaga dostępu do pamięci głównej:
call time
movl %eax, %edi #1
movl %eax, 12(%rsp) #2
call _Z5test1i
leaq 12(%rsp), %rdi #3
movl %eax, %ebx
call _Z5test2RKi
Linia #1 bezpośrednio używa wartości zwracanej w {[2] } jako argumentu dla test1
w edi
. Linia # 2 i #3 wepchnie wynik do pamięci głównej i umieści adres w pierwszym argumencie, ponieważ argument jest zadeklarowany jako odniesienie do int, a więc musi być możliwe np. pobranie jego adresu. Czy coś można obliczyć w całości za pomocą rejestrów lub potrzebuje dostępu do pamięci głównej może zrobić wielką różnicę w tych dniach. Tak więc, oprócz bycia bardziej do pisania, const int&
może być również wolniejszy. Zasadą jest przekazywanie wszystkich danych, które są co najwyżej tak duże, jak rozmiar słowa według wartości, a Wszystko inne przez odniesienie do const. Również przekazać argumenty template przez odniesienie do const; ponieważ kompilator ma dostęp do definicji szablonu, zawsze może zoptymalizować odniesienie.
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
2011-01-16 14:22:55
Zamiast" myśleć", że jest zoptymalizowany przez kompilator, dlaczego nie weźmiesz listy asemblera i nie dowiesz się na pewno?
Śmieci.c++:
int my_int()
{
static int v = 5;
return v;
}
const int& my_int_ref()
{
static int v = 5;
return v;
}
Wygenerowane wyjście asemblera (elided):
_Z6my_intv:
.LFB0:
.cfi_startproc
.cfi_personality 0x3,__gxx_personality_v0
movl $5, %eax
ret
.cfi_endproc
...
_Z10my_int_refv:
.LFB1:
.cfi_startproc
.cfi_personality 0x3,__gxx_personality_v0
movl $_ZZ10my_int_refvE1v, %eax
ret
movl
instrukcje w obu są bardzo różne. Pierwszy przesuwa 5
do EAX
(który jest rejestrem tradycyjnie używanym do zwracania wartości w kodzie x86 C) , a drugi przesuwa adres zmiennej (dla jasności) w EAX
. Oznacza to, że funkcja wywołująca w pierwszym przypadku może bezpośrednio użyć operacji rejestru bez uderzania pamięci, aby użyć odpowiedzi, podczas gdy w drugim musi uderzyć pamięć przez zwrócony wskaźnik.
Więc wygląda na to, że nie jest zoptymalizowany.
To ponad innymi odpowiedziami, które tu otrzymałeś wyjaśniając, dlaczego T
i const T&
nie są wymienne.
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
2011-01-16 13:50:27