Po co w C++... referencje?

0

Cześć ;)
Pytanie dziwne, ale uzasadnione. Zauważyłem, że referencje w C++ są używane tylko do przekazywania przez wartość. Ja nawet nie bardzo zdaję sobie sprawę jak mogę coś przypisać do referencji. Po co w ogóle te referencje. Przekazywać argumenty można przez wskaźniki.

3

Referencje są "mocniejsze" od wskaźników i kompilator może bardziej popisać się optymalizacją.

2

Po co w ogóle te referencje. Przekazywać argumenty można przez wskaźniki.
Można też przez referencje, bez babrania się w nieskończone dereferencjonowanie :)

0

coś więcej?

3

Zauważyłem, że referencje w C++ są używane tylko do przekazywania przez wartość.

referencje, jak sama nazwa wskazuje, służą do przekazywania przez referencję.

coś więcej?
Referencja nigdy nie będzie miała wartości NULL. Zawsze masz zagwarantowane, że odnosi się do obiektu. Nie trzeba więc tego sprawdzać, tak jak w przypadku wskaźników.

1

Załóżmy, że mamy wskaźnik na obiekt jakiejś klasy, jeśli wywołamy jakąś metodę na tym obiekcie, a wskaźnik będzie NULL'em, to program się wsypie. Można temu zaradzić za każdym razem sprawdzając, czy wskaźnik nie jest NULL'em, ale gdy kodu jest dużo zwyczajnie można o tym zapomnieć. Teraz mamy zamiast wskaźnika referencje, referencja zawsze wskazuje na coś, więc nie może być NULL'em (pomijając dziwne sztuczki, ale tego nikt nie robi), więc problem znika. To jest plus referencji. Minus referencji jest taki, że nie można jej przypisać drugi raz, to znaczy jeśli referencja już na coś wskazuje, to nie można zmienić na co wskazuje.

Ogólnie to zasada jest taka, że o ile się da, to warto używać referencji.

http://yosefk.com/c++fqa/ref.html - tu znajdziesz mnóstwo informacji o referencjach i problemach z nimi związanymi.

0

Przekazywać argumenty można przez wskaźniki, ale referencja ma to do siebie, że łatwiej "ukryć" to co się znajduje w funkcji do której przekazujemy te argumenty.
Moim zdaniem prościej jest napisać jako wywołanie funkcji:
funkcja(a,b) jeżeli jej deklaracja wygląda np tak:
void funkcja(int &a, int &b),
niż funkcja(&a,&b) przy deklaracji void funkcja(int *a, int *b).
Jestem na etapie nauki, ale z tego co zdążyłem się już dowiedzieć ukrywanie implementacji klas czy funkcji wewnątrz klas jest bardzo ważne z punktu widzenia przenośności kodu, a referencje to ułatwiają.

0

@procek225 bzdury o_O Równie dobrze mógłyś powiedzieć odwrotnie no bo jak masz wskaźniki to zamiast funkcja(a,b) musisz zrobić funkcja(*a,*b). A referencje są o tyle niebezpieczne, że użytkownik może się nie zorientować że funkcja może modyfikować oryginalne argumenty które do niej przekazujesz.

0

@Shalom, wtedy żeby się zabezpieczyć użyłbym const przy deklaracji (tak uczą w mądrych książkach :) ), niestety nie mam doczynienia z profesjonalnym kodem, jestem samoukiem.

0
procek225 napisał(a):

niestety nie mam doczynienia z profesjonalnym kodem, jestem samoukiem.

Wbrew pozorom profesjonalny kod często jest niższej jakości niż ten hobbystyczny - pogoń za deadline'ami czy niedobór programistów w zespole nie sprzyjają jakości. :)

Referencja nigdy nie będzie miała wartości NULL. Zawsze masz zagwarantowane, że odnosi się do obiektu. Nie trzeba więc tego sprawdzać, tak jak w przypadku wskaźników.

To nie jest prawda. Czasem funkcja może wymagać referencji, a użytkownik potrzebuje przekazać w tym argumencie jakąś zmienną, która jest na stercie. Jeśli się pomyli - przy odwołaniu do referencji mamy nieprawidłową operację.

void foo(int& a)
{
    if( (&a) == nullptr )
        cout << "bad ref" << endl;
    else
        cout << "good ref to: " << a << endl;
}

int main()
{
    int* a = nullptr;
    //...
    foo(*a);
    return 0;
}

Podobnie jest z przekazywaniem referencji do funkcji/klas, gdzie jest potencjalna możliwość, że długość życia przekazywanego elementu jest krótsza niż jego okres użycia. Wcale nie ma gwarancji, że referencja nadal będzie prawidłowa. Od takich rzeczy są inteligentne wskaźniki.

Referencje służą głównie do przekazywania przez referencję wtedy, gdy chcemy uniknąć kopiowania drogich obiektów (jako argumenty lub jako wartość zwracana) lub gdy istnieje dużo argumentów wyjściowych (lub wejściowych i wyjściowych jednocześnie). Jeśli referencja jest zwracana z jakiejś funkcji jest to też wskazówka dla użytkownika, że pamięć ta zarządzana jest przez daną funkcję (klasę) - co w przypadku wskaźników jest nieoczywiste - i zabezpiecza przed jej zwolnieniem. Nadal jest to możliwe, ale większość programistów zorientuje się, że nie jest to wskazane.

int& foo();
...
int a = foo();
delete (&a); //nope!
0
Wybitny Pomidor napisał(a):

Cześć ;)
Pytanie dziwne, ale uzasadnione. Zauważyłem, że referencje w C++ są używane tylko do przekazywania przez wartość. Ja nawet nie bardzo zdaję sobie sprawę jak mogę coś przypisać do referencji. Po co w ogóle te referencje. Przekazywać argumenty można przez wskaźniki.

Referencje są dobre ze względów jedynie kosmetyczno-optymalizacyjnych (w sensie skrócenia kodu).

Zwykle gdy mamy jakąś tam niewielką tablicę, np. o rozmiarze 4, czy 8,
na której elementach wyliczamy coś intensywnie, no to wówczas można sobie to skrócić,
stosując właśnie referencję.

przykładowo:

double compute(double t[])
{
 double &a = t[0], &b = t[1], &c = t[2], &d = t[3];

 a = a*pi + b/2 - c;
 c = a + b*c*1.5 - d;
 c -= d/7 * 8/d;
 return a+b+c+d;
}

no, czyli zamiast używać pełno tych kwadraciaków: t[0] = t[0] + t[1]/2 - t[2]... itd. mamy bardzo czytelny, skrócony kodzik na 4-ech a,b,c,d.

Można oczywiście przekazać te 4-ry zmienne wprost - o tak:
double compute(double &a, double &b, double &c, double &d)

no ale wtedy będzie to zwykle wolniej działać... zwłaszcza dla większej liczby zmiennych, np. 16: a,b,c,d,e,f, g,h, i, j...
bo w tej wersji przekazujemy aż 16 parametrów - adresów, poprzez stos zamiast tylko jednego.

0

Czy kod powyżej to nie jest przypadkiem UB? Zyski o których mówisz to imo jakaś drobnica ( o ile w ogóle takie rozwiązanie niesie za sobą jakiekolwiek korzyści ) o której nawet się nie myśli + kompilator wie lepiej, no i nie wiem kto deklaruje funkcje o 16 argumentach zamiast po prostu przekazać przez pointer. - Proxima 1 godz. temu

Kod jest całkowicie poprawny - jednoznaczny,
i można to traktować jedynie jako rodzaj optymalizacji/kosmetyki, jak mówiłem.

W C++ można zupełnie zaniechać referencji, bo jest ona w pełni do zastąpienia wskaźnikami, czyli i z tablicami.

Niemniej nawet taka 'optymalizacja' jest niekiedy bardzo pomocna,
i sam często takie triki stosuję, np. przy w kodowaniu obrazków jpg mamy takie tablice danych,
które najlepiej podmienić referencjami, zamiast wymisywać z 40 razy [...],
a wtedy kod (ten pisany - źródłowy) skraca się istotnie.
(transformacje DCt i odwrotna: iDCt: dct = discrete cosine transform).

Referencje do wskaźników mają podobne zastosowanie, tz. pozwalają na skrócenie kodu,
np. w przypadku operacji na drzewach, oraz poprawiają czytelność...
no i chyba w zasadzie nic poza tym z tej referencji nie można wycisnąć.

0

W sytuacjach kiedy możemy przekazać argument do funkcji przez referencję zamiast przez wskaźnik, możemy zyskać na tym iż nie musimy definiować wskaźnika (rezerwować pamięci) oraz uniknąć jego niejawnego kopiowania.

0
Krzywy Młot napisał(a):

W sytuacjach kiedy możemy przekazać argument do funkcji przez referencję zamiast przez wskaźnik, możemy zyskać na tym iż nie musimy definiować wskaźnika (rezerwować pamięci) oraz uniknąć jego niejawnego kopiowania.

W tym przypadku nie ma to żadnej różnicy.

fun(int *p_to_a)

jest dokładnie tym samym co:

fun(int &a);

tz. kod finalny będzie identyczny.

0

Musi i dlatego też jest.

Kompilator, czy też sam procesor, używa tylko adresów do zmiennych (obszarów pamięci).
Zatem dowolna referencja to po prostu adres (znaczy liczba uint), na poziomie maszynowym, czyli wskaźnik de facto!

Są oczywiście jeszcze te tzw. rejestry maszyny, które są także zmiennymi,
a nie mają adresu... no ale to jest raczej już zbytnio zaawansowane... jak dla programistów c++, czy innych tego typu (zwłaszcza java). :)

0

Nie musi, to szczegół implementacyjny.
Jeżeli twierdzisz inaczej, wskaż cytat z dokumentacji języka.

0
Patryk27 napisał(a):

Nie musi, to szczegół implementacyjny.
Jeżeli twierdzisz inaczej, wskaż cytat z dokumentacji języka.

A co konkretnie nie musi?

rejestry procesora to nie są żadne zmienne... (zmienną cechuje m.in adres, a sam powiedziałeś że rejestry nie mają adresów, to akurat prawda) -

Rejestry są zmiennymi, a do tego takimi najlepszymi - wymarzonymi dla programisty!

Stąd też kod typu:

register int x = v, ret;

w którym to 'register' jest oczywiście zwykle ignorowane przez obecne kompilatory;

no, ale zawsze można to przeskoczyć... tylko że kosztem przenośności:

asm {
mov eax, v
// i teraz już to eax jest zmienną v
add eax, 7; // v + 7
...
mov ret, eax; // ostateczny wynik kopiujemy do v
}

return ret;

6

A co konkretnie nie musi?

Referencja nie musi być implementowana jako wskaźnik.

i teraz już to eax jest zmienną v

Sam jesteś zmienną v. Ta instrukcja jedynie ładuje do rejestru wartość z pamięci gdzie znajduje się zmienna v. Jak zmienisz tą zmienną to w rejestrze nadal będzie stara wartość. Rejestry nie są zmiennymi.

0
Shalom napisał(a):

Referencja nie musi być implementowana jako wskaźnik.

Musi być, bo to jest dokładnie samo na poziomie kodu wynikowego.

Shalom napisał(a):

Sam jesteś zmienną v. Ta instrukcja jedynie ładuje do rejestru wartość z pamięci gdzie znajduje się zmienna v. Jak zmienisz tą zmienną to w rejestrze nadal będzie stara wartość. Rejestry nie są zmiennymi.

Nie improwizuj synek.

Rejestry są z założenia zmiennymi roboczymi każdego procesora bez wyjątku.

A ram wymyślono głównie dlatego, że liczba rejestrów jest drastycznie ograniczona.
Niemniej wszelkie obliczenia są realizowane nadal na rejestrach, a nie bezpośrednio w ram.

w twoim ramowym komputerze byłoby możliwe np. coś takiego
add x,y

konkretnie nie ma x ani y, lecz coś takiego: [adr_x], [adr_y], czyli adresowanie;
co nazwano referencją - do pamięci.

no, ale obecnie nie istnieje nawet taka instrukcja, bo musi to być realizowane na rejestrach:

mov r, x
add r, y
mov x, r

2

Chyba nadal nie nadążasz.

Standard języka C++ nie wymaga implementowania referencji jako wskaźników - wpadli na to dopiero twórcy kompilatorów, bo tak było wygodniej.

A teraz przypuśćmy, że kompilujemy C++ na jakąś maszynę wirtualną albo architekturę, gdzie występuje wskaźnik i referencja jako odrębny byt, i co? Nagle Twoja definicja się sypie.
Dlatego właśnie zakładanie, że wskaźnik=referencja jest UB - bo nie wymaga tego standard.

Czymś podobnym jest opieranie się w programach o istnienie VMT i wyczynianie cudów-niewidów mających zoptymalizować program.

Tutaj masz ostateczny dowód, cytat z ISO C++:

1.7 The C++ memory model 3 [...] Various features of the language, such as references and virtual functions, might involve additional memory locations that are not accessible to programs but are managed by the implementation. [...]

0

@fju ech. Rozumiem że o takich rzeczach jak "maszyny stosowe" nie słyszałeś? A szkoda, bo większość maszyn wirtualnych to właśnie maszyny stosowe a nie rejestrowe. A trzeba ci wiedzieć że da sie kompilować różne języki do maszyn wirtualnych. C/C++ też -> http://nestedvm.ibex.org/ i co teraz? Skoro jvm nie ma rejestrów? ;] Tak samo ma się sprawa z implementacją referencji. Większość kompilatorów na x86 faktycznie implementuje referencje jako wskaźnik, ale wcale nie musi. Ciekawostka -> stare JVMy wcale nie implementowały javowych referencji jako wskaźników, więc gdybyśmy skompilowali kod w C na takiego jvma to co? ;]

0
Patryk27 napisał(a):

Chyba nadal nie nadążasz.

A niby za czym miałbym nadążać, skoro nie potrafisz ściśle - formalnie zadać pytania?

Patryk27 napisał(a):

Standard języka C++ nie wymaga implementowania referencji jako wskaźników - wpadli na to dopiero twórcy kompilatorów, bo tak było wygodniej.

A to bardzo ciekawe... ale chyba tylko dla wirtualnych teoretyków... od niebytów!
Niby jakie masz alternatywy w praktyce w tym temacie?!

Patryk27 napisał(a):

A teraz przypuśćmy, że kompilujemy C++ na jakąś maszynę wirtualną albo architekturę, gdzie występuje wskaźnik i referencja jako odrębny byt, i co? Nagle Twoja definicja się sypie.
Dlatego właśnie zakładanie, że wskaźnik=referencja jest UB - bo nie wymaga tego standard.

Naprawdę? No to pokaż mi realizację, tz. taką użyteczną - fizyczną, referencję bez adresu, w ogólności.

Patryk27 napisał(a):

Tutaj masz ostateczny dowód, cytat z ISO C++:

1.7 The C++ memory model 3 [...] Various features of the language, such as references and virtual functions, might involve additional memory locations that are not accessible to programs but are managed by the implementation. [...]

To jest taki sam śmieszny dowód jakich znamy pełno z fizyki teoretycznej.
Przykładowo: twierdzi się powszechnie że czas jest względny,
czego dowodzą zegary atomowe... bo one zwalniają.

No, ale one przecież zwalniają... tak zwyczajnie, dlatego to widzimy i mierzymy, tj. bezwzględnie. :)
Nie ma to najmniejszego wpływu na pojęcie samego czasu...

Konkludując w końcu:
te teoretyzowane referencje mają tyle wspólnego z realizowanymi referencjami,
co... zwalnianie zegarów z relatywizmem czasu. :))

0

Niby jakie masz alternatywy w praktyce w tym temacie?!

Maszyny wirtualne, które rozróżniają wskaźniki i referencje :)
Poza tym wciąż mylisz język C++ z jego kompilatorami - w C++ masz jasno rozgraniczone pojęcie wskaźnika oraz referencji, a to, że na x86 kompilatory sobie idą na skróty i w kodzie wynikowym jest to samo nie ma najmniejszego znaczenia.

Naprawdę? No to pokaż mi realizację, tz. taką użyteczną - fizyczną, referencję bez adresu, w ogólności.

Referencję bez adresu? Ale o czym Ty do mnie?
Wskaźnik != adres != referencja, w kontekście C++.

twierdzi się powszechnie że czas jest względny

"twierdzi się" - citation needed.
Kto tak twierdzi?

0
Patryk27 napisał(a):

Niby jakie masz alternatywy w praktyce w tym temacie?!

Maszyny wirtualne, które rozróżniają wskaźniki i referencje :)

Marny żart.
Nie masz nawet formalnej definicji maszyny wirtualnej.
A gdybyś spróbował coś takie zrobić, no to wylądujesz wprost na tej fizycznej maszynie,
czyli cały ten twój cyrk z wirtualizacją, i tak kończy się w punkcie startu.

Patryk27 napisał(a):

Poza tym wciąż mylisz język C++ z jego kompilatorami - w C++ masz jasno rozgraniczone pojęcie wskaźnika oraz referencji, a to, że na x86 kompilatory sobie idą na skróty i w kodzie wynikowym jest to samo nie ma najmniejszego znaczenia.

Nie mylę c++ z kompilatorami, a jedynie uwzględniam prosty fakt:
kompilator jest konieczny.
Sam język bez kompilatora, czyli tego dekodera symboli na ich realne odpowiedniki, nie ma żadnego sensu, i dlatego nie istnieje.

Język czysto formalny, w 100% odrealniony - zupełnie niezależny od fizycznej realizacji?
A niby co to miałoby być?!
Nie ma takich rzeczy.

twierdzi się powszechnie że czas jest względny

"twierdzi się" - citation needed.
Kto tak twierdzi?</quote>

Ano wielu, w zasadzie większość amatorów... w tym m.in ten gościu:

Nie "twierdzi się", ale czas jest względny i jest to potwierdzone - ten model całkowicie pokrywa się z rzeczywistością. Uważasz też że Ziemia jest płaska? :) Bez tej śmiesznej fizyki teoretycznej nie miałbyś chociażby GPS-a. - Websplash dzisiaj, 09:34

:)

Przy okazji odpowiem na te naiwne wyskoki:
Ziemia jest płaska tak samo jak i okrągła, czy śrubowa... wedle tej logiki relatywnej (tj. odrealnionej == nieistniejącej);
bowiem czaso-przestrzeń jest względna (wedle tej teorii) - nieprawdaż? :)

1

kompilator jest konieczny.
Sam język bez kompilatora, czyli tego dekodera symboli na ich realne odpowiedniki, nie ma żadnego sensu, i dlatego nie istnieje.

A słyszałeś o czymś takim jak interpreter?
Czym wtedy jest "realny odpowiednik"?

0
Azarien napisał(a):

kompilator jest konieczny.
Sam język bez kompilatora, czyli tego dekodera symboli na ich realne odpowiedniki, nie ma żadnego sensu, i dlatego nie istnieje.

A słyszałeś o czymś takim jak interpreter?
Czym wtedy jest "realny odpowiednik"?

kompilator jest przecież interpreterem w sensie formalnym.
Tylko że specyficznym: takim który tłumaczy całość od razu, zamiast 'na żywo'.
Co ma swoje zalety, no ale i wady...

0

no to wylądujesz wprost na tej fizycznej maszynie,
czyli cały ten twój cyrk z wirtualizacją, i tak kończy się w punkcie startu.

Ale Ty cały czas mówisz o x86, a ja przypadku ogólnym ;)

0
Patryk27 napisał(a):

no to wylądujesz wprost na tej fizycznej maszynie,
czyli cały ten twój cyrk z wirtualizacją, i tak kończy się w punkcie startu.

Ale Ty cały czas mówisz o x86, a ja przypadku ogólnym ;)

Każdy komputer = pamięć + procesor; a nawet generalnie: dowolna maszyna.
I można w ten schemat wsadzić nie tylko komputer, ale i mózg jak i proste automaty, typu spłuczki klozetowej (w tym przypadku mamy chyba zero ramu, a procesor jest tu prostym układem mechanicznym, który realizuje tylko jedną instrukcję). :)

0

Każdy komputer = pamięć + procesor; a nawet generalnie: dowolna maszyna.

No i co z tego? :)

0

Referencja to tylko inna nazwa na adresowanie.

Niekiedy można jedynie uniknąć pośredniego adresowania, jak w przypadku:

int &x = v[679].b[7].z;

// i teraz:
x += 2xx - 7*x + 1;

chyba jest jasne, że tu nie potrzeba tworzyć adresu do tej referowanej zmiennej z.

no ale w ogólnym przypadku trzeba, niestety.

1 użytkowników online, w tym zalogowanych: 0, gości: 1