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?

Author: Pijusn, 2011-01-16

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).

 126
Author: 6502,
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 zamiast const T& wymaga, aby T 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.
 13
Author: Peter Alexander,
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 .

 8
Author: Oliver Charlesworth,
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.

 7
Author: Philipp,
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.

 3
Author: JUST MY correct OPINION,
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