Dobre nawyki w programowaniu – const corectness
April 7, 2011, 6:05 PM in Programming, Technology

Język C++ pierwszy raz zobaczyłem na oczy w 3 klasie gimnazjum, czyli jakieś 6 lat temu. Od tego czasu coś tam sobie grzebałem w tym języku, mniej lub bardziej. Jednak tak poważniej zacząłem programować od jakichś 3 lat. Wcześniej nie zwracałem uwagi na optymalność kodu, prędkość działania, chociaż o czytelność kodu zawsze dbałem ;) Dużo dały mi tutaj studia, które mimo że tak naprawdę nie nauczyły mnie niczego nowego, to zmusiły do refleksji nad moim stylem pisania. Postanowiłem pisać bardziej profesjonalnie.

Jedną z zasad poprawnego pisania kodu w C++ jest tzw. zasada “const corectness” która mówi o tym gdzie powinno się stosować słowo kluczowe const, a gdzie nie. Oczywiście ktoś może powiedzieć, że const oznacza że coś ma nie zmieniać wartości, więc jeśli mam stałą zmienna to dam przed nią const i po problemie. Nie jest to podejście złe, ale dość naiwne, ponieważ const ma także inne zastosowania.

Może jeszcze zanim zacznę wymieniać zastosowania const, wytłumaczę czym różni się zapis

  1. const int PI = 3.14;

od

  1. #define PI 3.14

Oczywiste jest, że różnią one się sposobem w jaki deklarujemy stałą, bo w pierwszym przypadku mamy zmienną o stałej wartości, a w drugim przypadku informację dla preprocesora, że należy wszystkie wystąpienia “PI” zamienić na “3.14”. Dla kompilatora jednak jest to duża różnica, w pierwszym przypadku kompilator widzi że ma do czynienia ze zmienną o stałej wartości i może tej informacji użyć, w drugim przypadku kompilator widzi tylko “3.14”, tak jakby użytkownik tam wpisał tą liczbę.

Skoro to już wytłumaczone, to może przejdę do zastosowań const. Const można stosować przed zmienną, po znaku * w deklaracji wskaźnika, na liście argumentów funkcji oraz w przy funkcjach w programowaniu obiektowym.

Spójrzmy na te trzy linijki kodu:

  1. const int *ptr;
  2. int *const ptr;
  3. const int *const ptr;

W pierwszym przypadku mamy const przed nazwą zmiennej, czyli ptr jest zmiennym wskaźnikiem na stałego inta, możemy zmienić obiekt na który będzie wskazywał ptr, ale nie samą wartość obiektu.
W drugim przypadku mamy const po znaku *, czyli mamy stały wskaźnik na zmienny int, oznacza to, że nie możemy zmienić obiektu na który on wskazuje, możemy jednak dalej zmieniać wartość tego obiektu.
W trzecim przypadku mamy połączenie obu powyższych linijek, mamy stały wskaźnik na stały obiekt. Nie zmienimy ani wskaźnika, ani wartości obiektu.

Zdaję sobie sprawę, że różnice są bardzo subtelne, dlatego pokaże przykład który powinien rozwiać niepewności:

  1. int *ptr; // zwykły wskaźnik na inta
  2. *ptr = 0; // zmieniam wartość inta na który wskazuje wskaźnik
  3. ptr = 0; // zmieniam adres int na który wskazuje wskaźnik
  4.  
  5. const int *constInt; //wskaźnik na stałego inta
  6. *constInt = 0; // chcę zmienić wartość stałego inta – nie przejdzie
  7. constInt = 0; // zmieniam adres wskaźnika – nie ma problemu
  8.  
  9. int *const constPtr; //stały wskaźnik na zmiennego inta
  10. *constPtr = 0; // zmieniam wartość inta – nie ma problemu
  11. constPtr = 0; // chciałem zmienić adres wskaźnika – nie da rady
  12.  
  13. const int *const constIntPtr; // stały wskaźnik na stałego inta
  14. *constIntPtr = 0; // nie da rady
  15. constIntrPtr = 0; // kit :( (a może :) bo przecież tak napisaliśmy kod)

No to teraz opowiem o używaniu const na liście argumentów funkcji. Normalnie gdy deklarujemy funkcję:

  1. void doSomething(int data);

do funkcji zostanie wysłana kopia zmiennej, dlatego zmiany na niej nie będą widoczne poza tą funkcją, aby tego uniknąć wysyłamy zmienne przez referencje:

  1. void doSomething(int &data);

Co jednak jeśli nie chcemy by nasza zmienna mogła być zmieniana, ale jest to duży obiekt i zrobienie jego kopii zajęłoby za dużo czasu/zasobów? Const przybywa na pomoc! Jeśli zdeklarujemy naszą funkcję jako:

  1. void doSomething(const int &data);

do funkcji zostanie wysłana stała referencja, czyli nie będziemy mogli zmienić nic w tej zmiennej i nie będzie robiona kopia.

Ostatnie zastosowanie const o jakim dzisiaj wspomnę to const w funkcjach przy programowaniu obiektowym. Weźmy taki przykład:

  1. class Point
  2. {
  3.   public:
  4.   ()
  5.     void setX(const int &x) { m_x = x; }
  6.     int x() const { return m_x; }
  7.  
  8.   private:
  9.     int m_x;
  10.     int m_y;
  11. };

Jak widać zgodnie z poprzednim punktem funkcja setX przyjmuje stałą referencję, ale w tym przykładzie uwagę należy zwrócić na const przy funkcji x(). W taki sposób użyte const mówi kompilatorowi, że w tej funkcji nie będziemy zmieniać wartości pól klasy (w tym przypadku m_x i m_y). Jest to także użyteczne dla programisty, jeśli przez przypadek będziemy jednak chcieli coś zmienić w klasie z tej funkcji, kompilator nam na to nie pozwoli, użyteczne ;)

I to na tyle na temat używania const w C++. Może niedługo znowu napiszę coś o pisaniu profesjonalnego kodu. ;)



Comments

Wladziu

Fajnie opisane.
Od siebie dodam ze słówko “mutable” przed argumentem w klasie, pozwoli na jego zmianę pomimo zastosowania “const” za nawiasami funkcji.

February 2, 2013, 1:16 PM Reply
Rachel92

Dzięki wielkie, nigdzie nie znalazłem tego tak dobrze opisanego w mega tutorialu i w symfoni tego nie ma, a ja jestem samoukiem.

Propozycja do następnego tutoriala : słówko static , bo tak samo brakuje opisu jak dobrze je stosować i kiedy.

Pozdrawiam !

March 23, 2013, 12:43 AM Reply


Comment

Name (required)
Email (required)
Homepage