Samodzielna nauka jest w wielu przypadkach bardzo ciekawym doświadczeniem, z co najmniej dwóch powodów:

  • satysfakcja z udanej eksploracji nieznanych dotąd zasobów wiedzy.
  • możliwość samodzielnego wyboru tempa i zakresu informacji opracowywanych podczas jednej “lekcji”.
Ma jednak jedną podstawową wadę - nikt poza uczniem nie weryfikuje poprawności przetwarzanych danych, a także ich zrozumienia. Dlatego czasem, pomimo tego, że nierzadko w wielu sprawach potencjalny uczeń ma rację, to i tak trzeba go “wyprostować”, wskazać inny, bardziej poprawny, sposób myślenia. Do czego zmierzam? Jak zwykle, chcąc podzielić się z innymi nieco wiedzą, do jednego z moich doświadczeń z przeszłości.

Moim jedynym “kwalifikowanym” kursem programowania były w tamtym czasie jedynie zajęcia informatyki w liceum, gdzie nauczyłem się całkiem nieźle programować w Pascalu i Delphi [tak, tak, wiem, Object Pascal - możesz już usiąść ;]]. Nie miałem więc okazji konsultować swojej wiedzy z kimś, kto w tej kwestii ma jakieś udokumentowane doświadczenie [Nie, tu w żadnym wypadku nie ma ofensywy w kierunku nauczyciela, po prostu wtedy nie miałem jeszcze pojęcia w którą stronę zawiedzie mnie morze moich zainteresowań, nie miałem też takich pomysłów jak teraz]. Dlatego też cały bagaż doświadczenia, jakie zdobyłem w pracy z językami innymi niż Pascal jest tylko i wyłącznie efektem czytania, pytania Wujka Google, pytania znajomych programistów, czytania, pytania Wujka Google, pytania znajomych programistów, itp., itd. Teraz nikogo nie powinno już dziwić, dlaczego wskazuję swoje, chociaż jak najbardziej niezamierzone, ale jednak błędy. Ku przestrodze dla potomnych.

Jako pierwszy wrzucam na stos sposób przekazywania zmiennych w parametrach funkcji / metod. Problem nie był irytujący, dopóki nie zaczął mnie interesować taki mały szkopuł jak szybkość działania kodu. Opiszę przypadek na przykładzie typu std::string, który, z powodu wielu operacji na łańcuchach znaków zaczął wywoływać u mnie wtedy dotkliwy ból głowy wyświetlając czas wykonania programu. Do tej pory nie widziałem nic złego w deklaracjach:

std::string retS(std::string str)
    {
    return str;
    }

std::string str = retS("foo");
class Foo</pre>
    {
    private std::string foos;

    public Foo(std::string x)
        {
        this->foos = x;
        }

    std::string getFoos(std::string str)
        {
        return str;
        }
 };

Foo *foo = new Foo("foos");
std::string foos = foo->getFoos();

Pewnego dnia zobaczyłem jednak w kodzie kolegi dziwny [wtedy] zapis:

cppconst std::string&amp;

Myślę sobie standardowo: WTF? Szybka konsultacja z autorem kodu i wszystko jasne - trzeba się przeuczyć. Teraz, kiedy już jestem mądrzejszy, mogę napisać jakie są zalety tego rozwiązania. Otóż:

  • Pierwsze i najważniejsze, a jasne jak słońce: “&”, czyli operator referencji powoduje, że przesyłany jest jedynie adres zmiennej zamiast jej kopii lokalnej, tzn. nie zostaje wywołany konstruktor kopiujący i nie tracimy czasu i pamięci na utworzenie kopii dla miejsca docelowego użycia zmiennej. W tym przypadku po prostu nie będziemy kopiować stringa, żeby przekazać gdzieś drugi taki sam, ale damy mu gotowy obiekt.
  • Drugie, nie mniej ważne: Jeśli zmienna nie będzie modyfikowana w miejscu, do którego jest przekazywana, dlaczego nie mielibyśmy nie ułatwić sobie pracy i nie nadać jej atrybutu const? W takim wypadku należy oszczędzić sobie czasu na szukanie błędu typu “dlaczego ten string zawiera jakieś śmieci, przecież nic z nim nie robiłem?!”, ponieważ kompilator automatycznie zaprotestuje przy każdej próbie naruszenia wartości tej zmiennej.
  • Po trzecie, zachowując obydwie powyższe własności, w miejsce zmiennej można nadal wstawić literał, czyli używać jej w taki sam sposób jak wymienionego na początku “gołego” typu std::string.
Tak więc “poprawny” kod brzmi następująco:

std::string retS(const std::string&amp; str)
    {
    return str;
    }

std::string str = retS("foo");
class Foo</pre>
    {
    private std::string foos;

    public Foo(const std::string&amp; x)
        {
        this->foos = x;
        }

    std::string getFoos(const std::string&amp; str)
        {
        return str;
        }
 };

Foo *foo = new Foo("foos");
std::string foos = foo->getFoos();

Słowo “poprawny” ująłem w cudzysłów, ze względu na to, że przekazywanie zmiennej w “zwykły” sposób nie jest niedozwolone, nie jest tez złą praktyką, ponieważ zawsze trzeba mieć na uwadze cel takiego działania. Jeśli potrzebujemy dwóch kopii zmiennej, to oczywiście nie podstawimy pod drugą referencji do pierwszego obiektu, tylko zainicjalizujemy ją jego wartościami. Opisuję to rozwiązanie dlatego, że w wielu przypadkach nie potrzebujemy jednak kopii i swobodnie możemy używać tej samej zmiennej, oszczędzając pamięć i skracając w dosyć znaczący sposób czas wykonania programu.

Mam nadzieję, że powyższe wyjaśnienie rozwiało większość wątpliwości dotyczących przekazywania zmiennych w kodzie. Chciałbyś coś dodać? Gdzieś się pomyliłem? Wiesz lepiej? Skomentuj ten wpis! ;]

Linki: [1]  http://pl.wikipedia.org/wiki/Literał - definicja “literału”. [2] http://pl.wikipedia.org/wiki/Referencja_(informatyka) - Wikipedia o referencjach.