css: set PRE’s max-width so it doesn’t stretch the viewport
[mina86.com.git] / posts / stalosc-fizyczna-i-logiczna-w-c.pl.html
blob5251976a9194b9c1642d4d46488fb60891b19a27
1 <!-- subject: Stałość fizyczna i logiczna w {C++} -->
2 <!-- date: 2008-03-21 13:48:41 -->
3 <!-- tags: const, mutable, c++, c -->
4 <!-- categories: Articles, Techblog, WEiTI -->
6 <p>Chciałbym zaprezentować pewien tekst, który przygotowałem na jeden z przedmiotów na studiach. Traktuje on o modyfikatorze <code>const</code> i <code>mutable</code> oraz określa pojęcie <i>stałości logicznej</i> czyli niezmienności zachowania obiektu, gdy patrzymy z zewnątrz. Może komuś się to przyda. Czekam również na wszelkie komentarze.
8 <!-- FULL -->
10 <h2>Deklarowanie stałych</h2>
12 <p>Stałość fizyczna występuje, gdy w momencie kompilacji określamy przy pomocy modyfikatora <code>const</code>, że jakiejś wartości nie należy zmieniać.
14 <pre>
15 const int foo = 42; /* Definicja "zmiennej" foo będącej stałą
16 liczbą całkowitą i inicjacja jej wartością 42. */
17 foo = 666; /* Błąd kompilacji */</pre>
19 <p>Modyfikator ten odnosi się do typu, co ma szczególne znaczenie przy
20 wskaźnikach:
22 <pre>
23 int bar, baz;
25 const int *ptr1 = &amp;bar; /* Wskaźnik na stały int. */
26 ptr1 = &amp;baz; /* Poprawne. */
27 *ptr1 = 666; /* Błąd kompilacji. */
29 int *const ptr2 = &amp;bar; /* Stały wskaźnik na int. */
30 ptr2 = &amp;baz; /* Błąd kompilacji. */
31 *ptr2 = 666; /* Poprawne. */
33 const int *const ptr3 = &amp;bar; /* Stały wskaźnik na stały int. */
34 ptr3 = &amp;baz; /* Błąd kompilacji. */
35 *ptr3 = 666; /* Błąd kompilacji. */</pre>
37 <p>Rzecz jasna mamy do dyspozycji także większe "zagłębienia"
38 wskaźników:
40 <pre>
41 const int **ptr4; /* Wskaźnik na wskaźnik na stały int. */
42 int *const *ptr5; /* Wskaźnik na stały wskaźnik na int. */
43 int **const ptr6; /* Stały wskaźnik na wskaźnik na int. */
45 const int *const *ptr7; /* Wskaźnik na stały wskaźnik na stały int. */
46 const int **const ptr8; /* Stały wskaźnik na wskaźnik na stały int. */
47 int *const *const ptr9; /* Stały wskaźnik na stały wskaźnik na int. */
49 const int *const *const ptrA; /* Stały wskaźnik na stały wskaźnik
50 na stały int. */</pre>
52 <p>Możnaby tak w nieskończoność, szczególnie, że mamy jeszcze
53 wskaźniki do funkcji… No ale, powstrzymam się. ;)
54 Przypominam, że w C oraz C++ typy czyta się "od prawej do
55 lewej".
58 <h2 id=mutability>Stałość a modyfikowalność</h2>
61 <p>Należy zwrócić uwagę, iż z faktu, że wskaźnik jest stałego typ nie
62 wynika, iż wskazywana wartość nie może się zmienić. Jest to częste,
63 a niesłuszne domniemanie. Najprostrzym przykładem może być kod:
65 <pre>
66 int foo = 42;
67 int *bar = &amp;foo;
68 const int *baz = &amp;bar;
69 /* baz ma wartość 42 */
70 *bar = 666;
71 /* baz ma wartość 666 */</pre>
73 <p>(Utrudnia to optymalizację różnych funkcji, które przyjmują dwa
74 wskaźniki do tego samego typu. Jeżeli tylko jeden ze wskaźników
75 wskazuje na typ stały kompilator nie wie, czy ten drugi nie wskazuje
76 na (przynajmniej w części) ten sam obszar. Aby temu zaradzić, w C99
77 (C++ tego nie ma) zostało dodane słówko <code>restrict</code>, które
78 odnosi się do wskaźników i określa, że wskazywana wartość nie może
79 być osiągalna przez inne wskaźniki.)
81 <p id="modify_const_objects">Różnie bywa z wartościami zadeklarowanymi
82 jako stałe. Mogą one zostać zapisane w niemodyfikowalenj
83 przestrzeni pamięci i próba zmiany ich wartości może spowodować coś
84 pokroju <span lang="en">segmentation fault</span>, ale równie
85 dobrze mogą być umieszczone w zwykłej przestrzeni
86 i wówczas sztuczka z rzutowaniem pozwoli na ich zmianę:
88 <pre>
89 const int foo = 42;
90 *const_cast&lt;int*&gt;(&amp;foo) = 666; /* niezdefiniowane zachowanie */
92 const_cast&lt;char*&gt;("bar")[2] = 'z'; /* niezdefiniowane zachowanie */</pre>
95 <h2>Stałość argumentów funkcji</h2>
97 <p>Deklarowanie argumentów (chodzi o sam argument, a nie ewentualne
98 wskazywane typy, a więc referencje odpadają z rozważań) funkcji jako
99 stałych nie wpływa na zewnętrzne zachowanie funkcji, gdyż i tak argumenty przekazywane są
100 przez wartość (o ile nie jest to referencja). Można to jednak
101 stosować, aby zapobiec zmianie wartości argumentów, co jest zalecaną
102 przez niektórych praktyką, np.:
104 <pre>
105 int add(const int a, const int b) {
106 a += b; /* Błąd kompilacji. */
107 return a + b; /* Poprawne. */
108 }</pre>
110 <p>Deklarowanie typów wskazywanych przez funkcje jako stałe ma za to
111 duży wpływ na zachowanie zewnętrzne programu
112 i <strong>należy</strong> je stosować wszędzie tam, gdzie jest to
113 możliwe. Tzn. jeżeli jakaś funkcja
114 przyjmuje wskaźnik lub referencję na argument, którego nie zamierza
115 modyfikować powinna wskazywany typ zadeklarować jako stały,
116 np.:
118 <pre>
119 int sum(unsigned n, const int *nums) {
120 int ret = 0;
121 while (n--) {
122 ret += *num++;
124 return ret;
127 static const int nums[] = { /* … */ };
128 /* … */
129 sum(sizeof nums / sizeof *nums, nums); /* gdyby w prototypie funkcja
130 miała typ `int*`, a nie `const int*` ta linijka spowodowałaby
131 błąd kompilacji. */</pre>
133 <p>Ponadto, często o wiele lepiej przekazywać argument przez stałą
134 referencję zamiast przez wartość, gdyż nie wymaga to kopiowania
135 całego obiektu, np.:
137 <pre>
138 int foo(const std::vector&lt;int&gt; &amp;vec) {
139 /* Rób coś na wektorze */
142 /* versus */
144 int foo(std::vector&lt;int&gt; vec) {
145 /* Rób coś na wektorze */
146 }</pre>
148 <p>W obu przypadkach, wołając funkcję, mamy pewność, iż wektor
149 przekazany jako argument nie zostanie zmodyfikowany (mowa
150 o zachowaniu na zewnątrz funkcji), ale przekazywanie wektora przez
151 wartość jest po prostu stratą czasu. Dotyczy to również innych
152 mniejszych i większych obiektów. Szczególnie, gdy definiujemy jakiś
153 szablon należy stosować mechanizm przekazywania argumentów przez
154 stałą referencję zamiast przez wartość, gdyż nie wiemy z jakim typem
155 będziemy mieli do czynienia. W szczególności klasa może nie mieć
156 (publicznego) konstruktora kopiującego.
159 <h2>Rzutowanie</h2>
162 <p>Jak można się domyślić, rzutowanie z typu bez
163 modyfikatora <code>const</code> na tym z takim modyfikatorem jest
164 automatyczne, np.:
166 <pre>
167 int foo = 42;
168 const int *bar = &amp;foo;</pre>
170 <p>Rzutowanie w drugą stronę nie jest już automatyczne i wymaga
171 zastosowanie operatora rzutowania:
173 <pre>
174 const int foo = 42;
175 int *bar = (int*)&amp;foo; /* styl C */
176 int *baz = const_cast&lt;int*&gt;(&amp;foo); /* styl C++ */</pre>
178 <p>Generalnie zalecany jest styl C++, gdyż w ten sposób jesteśmy
179 pewni, że rzutowanie zmieni jedynie stałość typu. Przykładowo,
180 gdybyśmy zmienili typ zmiennej <code>foo</code>, a zapomnieli zmienić typy
181 w operatorach rzutowania kompilator bez żadnych ostrzeżeń
182 skompilowałby rzutowanie w stylu C, ale zgłosiłby błąd przy
183 rzutowaniu w stylu C++, gdyż zmiana dotyczy nie tylko stałości
184 typu:
186 <pre>
187 const long foo = 42;
188 int *bar = (int*)&amp;foo; /* Skompiluje się. */
189 int *baz = const_cast&lt;int*&gt;(&amp;foo); /* Błąd kompilacji. */</pre>
191 <p>Pewien wyjątek stanowią literały ciągów znaków, który wywodzi się
192 z czasów, gdy w języku C nie było słówka <code>const</code>. Zasadniczo
193 literały ciągów znaków są typu <code>const char[]</code> jednak, aby nie
194 psuć tysięcy istniejących programów, przypisanie literału do zmiennej
195 typu <code>char*</code> jest poprawne. <strong>Nie</strong> oznacza
196 to jednak, iż literały takie można modyfikować! Następująca
197 instrukcja powoduje niezdefiniowane zachowanie: <code>char *foo = "foo"; foo[0] =
198 'F';</code> (problem ten został już poruszony przy
199 omawianiu <a href="#mutability">modyfikowalności</a>)
201 <p>Kolejnym aspektem, który może wydać się dziwny, jest fakt, iż
202 konwersja <code>Foo**</code> do <code>const Foo**</code> <em>nie</em> jest
203 automatyczna. Można powiedzieć, że stałość musi być dodana wszędzie
204 po prawej stronie (za wyjątkiem samej zmiennej) i konwersja
205 <code>Foo**</code> do <code>const Foo* const *</code> jest już dozwolona:
207 <pre>
208 void bar(const Foo **arr);
209 void baz(const Foo *const *arr);
211 int main(void) {
212 Foo **arr;
213 /* … */
214 bar(arr); /* Błąd kompilacji. */
215 baz(arr); /* Poprawny kod. */
216 /* … */
217 }</pre>
219 <p>Aby zrozumieć czemu tak jest należy przeanalizować poniższy
220 kod:
222 <pre>
223 const int x = 42;
224 int *p;
225 const int **pp = &amp;p; /* konwersja `int**` do `const int**`*/
226 *pp = &amp;x; /* `*pp` jest typu `const int*`, więc można mu
227 przypisać adres zmiennej `x`. */
228 /* w tym momencie `p` wskazuje na `x` (gdyż `pp`
229 wskazywało na `p`. */
230 *p = 666; /* `x` jest modyfikowane! */</pre>
234 <h2>Stałość w strukturach</h2>
236 <p>Stałe struktury (klasy) nie umożliwiają zmiany pól w ich wnętrzu.
237 Przykładowo, poniższy kod się nie kompiluje:
239 <pre>
240 struct Foo {
241 int bar;
244 const Foo baz = { 42 };
245 baz.bar = 666;</pre>
247 <p>Jednakże, jeżeli elementem struktury (klasy) jest wskaźnik jedynie
248 on sam staje się stały, ale wartość wskazywana nie, np.:
250 <pre>
251 struct Foo {
252 int *bar;
255 static int qux = 42, quux = 042;
256 const Foo baz { &amp;qux };
257 *baz.bar = 666; /* Poprawne. */
258 baz.bar = &amp;quux; /* Błąd kompilacji. */</pre>
260 <p>Jest to problem <a href="#logic_const">stałości logicznej</a>,
261 o której poniżej.
263 <p>Niestatyczne metody klas również mogą być zadeklarowane jako stałe.
264 Metody takie nie mogą modyfikować pól klasy (chyba, że posiadają
265 <a href="#mutable">modyfikator <code>mutable</code></a>, o którym niżej)
266 ani wołać innych metod, które nie są zadeklarowane jako stałe.
268 <pre>
269 struct Foo {
270 int get() { return bar; } /* [1] */
271 int get() const { return bar; } /* [2] */
273 int sum(int v) { return get() + v; } /* [3]; woła [1] */
274 int sum(int v) const { return get() + v; } /* [4]; woła [2] */
276 void set(int v) { bar = v; } /* [5] */
277 void set(int v) const { bar = v; } /* niepoprawne */
279 void add(int v) { set(sum(v)); } /* woła [3] i [5] */
280 void add(int v) const { set(sum(v)); } /* niepoprawne, woła
281 [4], ale [5] nie jest metodą stałą. */
283 private:
284 int bar;
285 };</pre>
287 <p>Czasami bywa tak, że metoda ma swoją wersję stałą i niestałą, które
288 robią identyczną rzecz, ale np.
289 jedna zwraca wskaźnik do stałego obiektu, a druga do niestałego
290 obiektu. Aby nie musieć pisać dwa razy tego samego kodu można
291 zastosować rzutowanie, np.:
293 <pre>
294 struct Foo {
295 /* … */
296 const int *find(int arg) const;
297 int *find(int arg) {
298 return const_cast&lt;int*&gt;(const_cast&lt;const Foo*&gt;(this)-&gt;find(arg));
300 /* … */
301 };</pre>
303 <p>lub
305 <pre>
306 struct Foo {
307 /* … */
308 const int *find(int arg) const {
309 return const_cast&lt;Foo*&gt;(this)-&gt;find(arg);
311 int *find(int arg);
312 /* … */
313 };</pre>
315 <p>Rzutowanie słówka <code>this</code> wymusza wołanie stałej lub
316 niestałej wersji danej metody. Bez niego mielibyśmy do czynienia
317 z niekończącą się rekurencją.
319 <p id="mutable">C++ wprowadza jeszcze jedno słowo kluczowe --
320 <code>mutable</code>, które oznacza, że dany element struktury może być
321 modyfikowany, w tej klasie nawet jeżeli jest ona stała.
322 Przykładowo:
324 <pre>
325 struct Foo {
326 int bar;
327 mutable int baz;
329 void mutate() const {
330 bar = 042; /* Błąd kompilacji. */
331 baz = 42; /* Kod poprawny. */
335 const Foo foo = { 42, 042 };
336 foo.bar = 666; /* Błąd kompilacji. */
337 foo.baz = 666; /* Kod poprawny. */</pre>
339 <p>Mechanizm ten powinien być stosowany tylko i wyłącznie dla pól,
340 które nie wpływają na zewnętrzny wygląd i zachowanie klasy (patrz
341 <a href="#logic_const">stałość logiczna</a>). Przykładowo, można
342 zaimplementować cache, w której przechowywane by były ostatnie
343 wyniki jakichś zapytań czy wyszukiwań:
345 <pre>
346 struct Foo {
348 /* … */
350 const int *find(int arg) const {
351 if (last_arg == arg) {
352 return last_found;
353 } else {
354 int *found;
355 /* Wyszukaj i ustaw `found` odpowiednio. */
356 last_arg = arg;
357 return last_found = found;
361 /* … */
363 private:
364 mutable int *last_found;
365 mutable int last_arg;
366 };</pre>
368 <p>Alternatywą dla słówka <code>mutable</code> byłoby rzutowanie, jednak
369 w różnych przypadkach może ono spowodować niezdefiniowane
370 zachowanie, <a href="#modify_const_objects">co zostało już
371 omówione</a>.
374 <h2 id="logic_const">Stałość logiczna</h2>
376 <p>Stałość logiczna to oczekiwanie, że zewnętrzne zachowanie i wygląd
377 jakiegoś obiektu nie ulegnie zmianie. Dla przykładu weźmy strukturę
378 do przechowywania liczb zespolonych:
380 <pre>
381 struct Complex {
382 double re, im;
383 };</pre>
385 <p>W tym przypadku zadeklarowanie jakiejś zmiennej jako stałej
386 gwarantuje nam stałość logiczną, np.:
388 <pre>
389 double abs(const Complex &amp;c) {
390 return hypot(c.re, c.im);
391 }</pre>
393 <p>Jednak w bardzo rzadkich przypadkach stałość fizyczna może
394 być <i>nadgorliwa</i>, np. jeżeli cacheujemy wyniki:
396 <pre>
397 struct Complex {
398 double re, im, abs;
399 bool abs_valid;
402 double abs(const Complex &amp;c) {
403 if (c.abs_valid) {
404 return c.abs;
405 } else {
406 c.abs_valid = true;
407 return c.abs = hypot(c.re, c.im);
409 }</pre>
411 <p>Powyższy kod oczywiście się nie skompiluje, ale z pomocą przychodzi
412 nam już wcześniej
413 opisane <a href="#mutable">słówko <code>mutable</code></a>.
415 <p>O wiele częściej stałość fizyczna jest <i>za mało gorliwa</i>.
416 Przykładowo, jeżeli mamy strukturę przechowującą ciągi znaków, to
417 spodziewamy się, iż po zadeklarowaniu zmiennej jako stała struktura
418 nie będzie możliwości zmieniać samego napisu, ale niestety tak nie
419 jest:
421 <pre>
422 struct String {
423 char *data;
424 unsigned length;
427 void modify(const String &amp;s) {
428 s.data[0] = 'A';
429 }</pre>
431 <p>W takich przypadkach należy odpowiednio obudowywać takie klasy
432 dodając im przeciążone akcesory istniejące zarówno w wersji jako
433 metoda stała oraz w wersji jako zwykła metoda.
435 <pre>
436 struct String {
437 char *getData() { return data; }
438 const char *getData() const { return data; }
439 unsigned getLength() const { return length; }
441 private:
442 char *data;
443 unsigned length;
444 };</pre>
446 <p>Oczywiście nadal wewnątrz stałej metody obiekty wskazywane przez
447 pole <code>data</code> mogą być modyfikowane i dlatego cała
448 odpowiedzialność, za utrzymanie stałości logicznej spada na
449 programiście, który implementuje daną klasę.
451 <!-- COMMENT -->
452 <!-- date: 2008-03-22 16:46:19 -->
453 <!-- nick: cysiek10 -->
455 <p>Świetny artykuł, teraz już wiem jak to jest z tym słówkiem const w C/C++, dziękuje
457 <p>Tutaj jest list Linusa na temat słówka const:<br />
458 http://kerneltrap.org/Linux/C_Semantics_Constants_and_Pointers
460 <!-- COMMENT -->
461 <!-- date: 2008-03-26 08:42:00 -->
462 <!-- nick: SirMike -->
463 <!-- nick_url: http://www.sirmike.org -->
465 <p>Fajne podsumowanie. Dzieki.
467 <!-- COMMENT -->
468 <!-- date: 2008-04-09 21:16:29 -->
469 <!-- nick: GiM -->
470 <!-- nick_url: http://gim.jogger.pl -->
472 <p>„W obu przypadkach, wołając funkcję, mamy pewność, iż wektor przekazany jako argument nie zostanie zmodyfikowany”
474 <p>No właśnie problem w tym, że nie mamy, to jest tylko wskazówka, nigdy nie wiadomo czy genialnemu autorowi danej metody/funkcji, nie przyszło do głowy zrobić sobie od tak const_cast, albo dajmy na funkcja/metoda, przyjmuje jako jeden z parametrów obiekt/strukturę, a element przekazywanej klasy/struktury ma atrybutu mutable.
476 <p>http://codepad.org/dwwyB6a3
478 <p>Poza tym, const wiąże się lewostronnie, dla wygody programistów zrobiono, że:<br />
479 const int **p = int const **p;<br />
480 Myślę, że część przykładów mogłabybyć czytelniejsza, gdybyś konsekwentnie używał lewostronnej wersji, np przykład<br />
481 z rzutowaniem <br />
482 Foo** =na= Foo const * const *<br />
483 staje się moim zdaniem automatycznie jaśniejszy.
485 <!-- COMMENT -->
486 <!-- date: 2009-01-08 18:41:23 -->
487 <!-- nick: mina86 -->
488 <!-- nick_url: http://mina86.com -->
490 <p>„No właśnie problem w tym, że nie mamy, to jest tylko wskazówka, nigdy nie wiadomo czy genialnemu autorowi danej metody/funkcji, nie przyszło do głowy zrobić sobie od tak const_cast”
492 <p>Oczywiście masz rację, ale milcząco założyłem, iż autor funkcji/metody poprawnie ją zaprojektował.
494 <p>„albo dajmy na funkcja/metoda, przyjmuje jako jeden z parametrów obiekt/strukturę, a element przekazywanej klasy/struktury ma atrybutu mutable.”
496 <p>Ponownie może źle się wyraziłem — faktycznie fizycznie obiekt może się zmienić, choć z naszego punktu widzenia nie ma to znaczenia, bo logicznie jest to ciągle ten sam obiekt przechowujący taką samą wartość (ponownie pomijam sytuacje, gdy klasa jest źle zaprojektowana).
498 <p>Co do lewostronności słowa kluczowego to już się przyzwyczaiłem i nie potrafię się przerzucić, choć próbowałem. Najbardziej przemawia do mnie przypadek, gdy pod funkcją zwracającą np. „Foo&amp;” deklaruję funkcję zwracającą „Foo const&amp;” — zastosowanie lewostronnego zapisu nazwy funkcji można ładnie wyrównać w kolumnach bez narzekania edytora na wcięcia. :)