You are on page 1of 71

IDZ DO

PRZYKADOWY ROZDZIA
SPIS TRECI

KATALOG KSIEK
KATALOG ONLINE
ZAMW DRUKOWANY KATALOG

TWJ KOSZYK
DODAJ DO KOSZYKA

CENNIK I INFORMACJE
ZAMW INFORMACJE
O NOWOCIACH
ZAMW CENNIK

CZYTELNIA
FRAGMENTY KSIEK ONLINE

C++. Inynieria
programowania
Autor: Victor Shtern
Tumaczenie: Daniel Kaczmarek (rozdz. 1 6), Adam
Majczak (rozdz. 7 11), Rafa Szpoton (rozdz. 12 19)
ISBN: 83-7361-171-1
Tytu oryginau: Core C++: A Software Engineering Approach
Format: B5, stron: 1084

Naucz si jzyka C++ w najlepszy sposb: poznajc go z punktu widzenia


inynierii programowania
Demonstruje praktyczne techniki stosowane przez zawodowych programistw
Zawiera poprawny, gruntownie przetestowany przykadowy kod rdowy
programw oraz przykady zaczerpnite z praktyki
Skoncentrowana na nowoczesnych technologiach, ktre musz pozna programici
Zawiera rady profesjonalistw, ktre pozwol czytelnikowi tworzy
najlepsze programy
Ksika Wiktora Shterna zatytuowana C++. Inynieria programowania stosuje wyjtkowy
sposb nauki jzyka C++ przeznaczony dla programistw majcych dowiadczenie
w dowolnym jzyku programowania: prezentuje moliwo zastosowania w C++
najlepszych technik programistycznych oraz metodologii inynierii programowania.
Nawet jeeli ju wczeniej wykorzystywae jzyk C++, ta wyczerpujca ksika przedstawi
sposb tworzenia poprawniejszego kodu, atwiejszego do utrzymania i modyfikacji. Ksika
niniejsza uczy zasad programowania obiektowego przed sam nauk jzyka, co pozwala
wykorzysta wszystkie zalety OOP do tworzenia poprawnych aplikacji. Udoskonalisz
znajomo kluczowych skadnikw standardu ANSI/ISO C++ rozpatrywanych z punktu
widzenia inyniera: klas, metod, modyfikatorw const, dynamicznego zarzdzania pamici,
zoe klas, dziedziczenia, polimorfizmu, operacji wejcia-wyjcia i wielu innych.
Jeeli pragniesz tworzy w jzyku C++ najlepsze programy, musisz projektowa, myle
i programowa stosujc najlepsze obecnie praktyki inynierii programowania.
Lektura ksiki C++. Inynieria programowania pomoe Ci w tym.
Ksika C++. Inynieria programowania kadzie nacisk na:

Wydawnictwo Helion
ul. Chopina 6
44-100 Gliwice
tel. (32)230-98-63
e-mail: helion@helion.pl

Prezentacj zastosowa zasad inynierii programowania w programach pisanych w C++


Tworzenie kodu atwego do pniejszych modyfikacji
Praktyczne zrozumienie zasad programowania obiektowego przed nauk
samego jzyka
Przedstawienie najnowszych cech standardu ANSI/ISO C++
Zaprezentowanie setek realistycznych przykadw kodu programw
Wiktor Shtern jest profesorem wykadajcym w collegeu przy uniwersytecie w Bostonie,
uznawanym za jedn z najlepszych w Stanach Zjednoczonych szk dla osb pracujcych
zawodowo. Oprcz wykadw z jzyka C++ na poziomie uniwersyteckim, Shtern prowadzi
rwnie zajcia praktyczne dla dowiadczonych programistw.
Dan Costello jest inynierem oprogramowania w firmie GE Marquette Medical Systems

Spis treci
Wprowadzenie

15

Co odrnia t ksik od innych ksiek o C++? ....................................................... 15


Dla kogo jest przeznaczona ta ksika? ..................................................................... 17
Jak korzysta z tej ksiki? ....................................................................................... 17
Konwencje stosowane w ksice............................................................................... 18
Dostp do kodw rdowych .................................................................................... 19

Cz I Wprowadzenie do programowania w jzyku C++


Rozdzia 1. Podejcie zorientowane obiektowo co je wyrnia?

21
23

rda kryzysu oprogramowania................................................................................. 24


Rozwizanie 1. wyeliminowanie programistw ........................................................ 28
Rozwizanie 2. ulepszone techniki zarzdzania ...................................................... 30
Metoda wodospadu ............................................................................................ 31
Szybkie tworzenie prototypu................................................................................. 32
Rozwizanie 3. projektowanie zoonego i rozwlekego jzyka .................................. 33
Podejcie zorientowane obiektowo czy dostaniemy co za nic?................................ 34
Na czym polega praca projektanta?...................................................................... 35
Jako projektu spjno ................................................................................ 37
Jako projektu czno ................................................................................ 37
Jako projektu wizanie danych i funkcji ......................................................... 38
Jako projektu ukrywanie informacji i kapsukowanie ....................................... 40
Sprawa projektowania konflikty nazewnictwa .................................................... 41
Sprawa projektowania inicjalizacja obiektu ....................................................... 42
Czym jest obiekt? ............................................................................................... 43
Zalety stosowania obiektw................................................................................. 44
Charakterystyka jzyka C++ ...................................................................................... 45
Cele jzyka C wydajno, czytelno, pikno i przenono................................ 45
Cele jzyka C++ klasy ze wsteczn zgodnoci z C............................................ 47
Podsumowanie ........................................................................................................ 50

Rozdzia 2. Szybki start krtki przegld jzyka C++

53

Podstawowa struktura programu ............................................................................... 54


Dyrektywy preprocesora............................................................................................ 56
Komentarze............................................................................................................. 60
Deklaracje i definicje ................................................................................................ 63
Instrukcje i wyraenia ............................................................................................... 69
Funkcje i wywoania funkcji ....................................................................................... 77

C++. Inynieria programowania


Klasy ...................................................................................................................... 86
Praca z narzdziami programistycznymi...................................................................... 90
Podsumowanie ........................................................................................................ 94

Rozdzia 3. Praca z danymi i wyraeniami w C++

95

Wartoci i ich typy.................................................................................................... 96


Typy cakowitoliczbowe ............................................................................................. 98
Kwalifikatory typu cakowitoliczbowego ............................................................... 100
Znaki ............................................................................................................... 104
Wartoci logiczne.............................................................................................. 106
Typy liczb zmiennoprzecinkowych............................................................................. 107
Praca z wyraeniami C++ ........................................................................................ 109
Operatory o wysokim priorytecie......................................................................... 110
Operatory arytmetyczne ..................................................................................... 111
Operatory przesunicia...................................................................................... 114
Bitowe operatory logiczne .................................................................................. 115
Operatory relacji i rwnoci................................................................................ 118
Operatory logiczne ............................................................................................ 120
Operatory przypisania........................................................................................ 122
Operator warunkowy.......................................................................................... 123
Operator przecinkowy ........................................................................................ 124
Wyraenia mieszane ukryte zagroenia ................................................................ 125
Podsumowanie ...................................................................................................... 131

Rozdzia 4. Sterowanie przebiegiem programu C++

133

Instrukcje i wyraenia ............................................................................................. 134


Instrukcje warunkowe ............................................................................................. 136
Standardowe formy instrukcji warunkowych......................................................... 136
Czste bdy w instrukcjach warunkowych........................................................... 140
Zagniedone instrukcje warunkowe i ich optymalizacja ....................................... 152
Iteracje ................................................................................................................. 158
Zastosowanie ptli WHILE ................................................................................. 159
Iteracje w ptli DO-WHILE .................................................................................. 167
Iteracje w ptli FOR........................................................................................... 170
Instrukcje skokw w C++........................................................................................ 173
Instrukcja BREAK.............................................................................................. 174
Instrukcja CONTINUE......................................................................................... 177
Instrukcja GOTO ............................................................................................... 178
Instrukcje skokw RETURN i EXIT....................................................................... 179
Instrukcja SWITCH ............................................................................................ 183
Podsumowanie ...................................................................................................... 186

Rozdzia 5. Agregacja za pomoc typw danych zdefiniowanych przez programist

187

Tablice jako agregaty homogeniczne........................................................................ 188


Tablice jako wektory wartoci............................................................................. 188
Definiowanie tablic C++ .................................................................................... 190
Operacje na tablicach ....................................................................................... 193
Sprawdzanie poprawnoci indeksw................................................................... 194
Tablice wielowymiarowe .................................................................................... 197
Definiowanie tablic znakw................................................................................ 200
Operacje na tablicach znakw............................................................................ 202
Funkcje acuchowe a bdy pamici .................................................................. 204

Spis treci

Dwuwymiarowe tablice znakw........................................................................... 208


Przepenienie tablic w algorytmach je wypeniajcych ........................................... 210
Definiowanie typw tablicowych ......................................................................... 214
Struktury jako agregaty heterogeniczne .................................................................... 216
Definiowanie struktur jako typw zdefiniowanych przez programist ...................... 216
Tworzenie i inicjalizowanie zmiennych strukturalnych ........................................... 217
Struktury hierarchiczne i ich komponenty ............................................................ 219
Operacje na zmiennych strukturalnych ................................................................ 220
Definiowanie struktur w programach zoonych z wielu plikw ............................... 222
Unie, typy wyliczeniowe i pola bitowe ....................................................................... 223
Unie ................................................................................................................ 223
Typy wyliczeniowe ............................................................................................. 227
Pola bitowe ...................................................................................................... 229
Podsumowanie ...................................................................................................... 233

Rozdzia 6. Zarzdzanie pamici stos i sterta

235

Zasig nazw jako narzdzie wsppracy.................................................................... 236


Zasigi leksykalne C++ ..................................................................................... 236
Konflikty nazw w tym samym zasigu ................................................................. 237
Stosowanie takich samych nazw w zasigach niezalenych .................................. 241
Stosowanie takich samych nazw w zasigach zagniedonych .............................. 241
Zasig zmiennych ptli ...................................................................................... 246
Zarzdzanie pamici klasy pamici.................................................................... 246
Zmienne automatyczne ..................................................................................... 248
Zmienne zewntrzne ......................................................................................... 251
Zmienne statyczne............................................................................................ 257
Zarzdzanie pamici zastosowanie sterty........................................................... 261
Wskaniki C++ jako zmienne o okrelonym typie ................................................. 263
Alokowanie pamici na stercie........................................................................... 268
Tablice i wskaniki ............................................................................................ 273
Tablice dynamiczne........................................................................................... 276
Struktury dynamiczne ........................................................................................ 290
Operacje wejcia i wyjcia na plikach....................................................................... 300
Zapisywanie do pliku......................................................................................... 301
Odczyt z pliku ................................................................................................... 304
Plikowe obiekty wejcia-wyjcia .......................................................................... 308
Podsumowanie ...................................................................................................... 311

Cz II Programowanie obiektowe w C++


Rozdzia 7. Programowanie w C++ z zastosowaniem funkcji

313
315

Funkcje w C++ jako narzdzie modularyzacji programu .............................................. 317


Deklaracje funkcji ............................................................................................. 318
Definicje funkcji ................................................................................................ 319
Wywoania funkcji ............................................................................................. 320
Promocja i konwersja argumentw funkcji ................................................................ 323
Przekazywanie parametrw do funkcji w C++ ............................................................ 326
Przekazanie parametru przez warto ................................................................. 326
Przekazanie parametrw poprzez wskanik ......................................................... 328
Przekazanie parametrw do funkcji charakterystycznej dla C++
poprzez referencj................................................................................... 336
Struktury .......................................................................................................... 341

C++. Inynieria programowania


Tablice............................................................................................................. 348
Wicej o konwersjach typw .............................................................................. 352
Zwracanie wartoci z funkcji .............................................................................. 355
Funkcje wplecione inline..................................................................................... 361
Parametry funkcji z wartociami domylnymi ............................................................ 364
Przecianie nazw funkcji........................................................................................ 370
Podsumowanie ...................................................................................................... 377

Rozdzia 8. Programowanie obiektowe z zastosowaniem funkcji

381

Kohezja................................................................................................................. 385
Sprzganie ............................................................................................................ 386
Niejawne sprzenie ......................................................................................... 386
Jawne sprzenie.............................................................................................. 390
Jak zredukowa intensywno sprzgania? ......................................................... 395
Hermetyzacja danych.............................................................................................. 400
Ukrywanie danych .................................................................................................. 407
Wikszy przykad hermetyzacji danych...................................................................... 413
Wady hermetyzacji danych przy uyciu funkcji ........................................................... 422
Podsumowanie ...................................................................................................... 425

Rozdzia 9. Klasy w C++ jako jednostki modularyzacji

427

Podstawowa skadnia definicji klasy ........................................................................ 430


Poczenie danych i operacji .............................................................................. 430
Eliminowanie konfliktw nazw ............................................................................ 435
Implementacja kodw metod poza definicj klasy................................................ 439
Definiowane obiektw przechowywanych w pamici rnych kategorii.................... 443
Kontrolowanie dostpu do komponentw klasy ........................................................ 444
Inicjowanie obiektw danej klasy ............................................................................. 451
Konstruktory jako metody.............................................................................. 452
Konstruktory domylne...................................................................................... 455
Konstruktory kopiujce...................................................................................... 457
Konstruktory konwersji ...................................................................................... 461
Destruktory ...................................................................................................... 463
Co i kiedy, czyli co naprawd robi konstruktory i destruktory ............................... 468
Widoczno nazw w obrbie klasy i przesanianie nazw przy zagniedaniu ............ 469
Zarzdzanie pamici za pomoc operatorw i wywoa funkcji ............................ 472
Zastosowanie w kodzie klienta obiektw zwracanych przez funkcje............................. 476
Zwrot wskanikw i referencji............................................................................. 476
Zwrot obiektw z funkcji .................................................................................... 479
Wicej o stosowaniu sowa kluczowego const .......................................................... 482
Statyczne komponenty klas .................................................................................... 488
Zastosowanie zmiennych globalnych jako charakterystyk klas .............................. 489
Czwarte znaczenie sowa kluczowego static ........................................................ 491
Inicjowanie statycznych pl danych..................................................................... 492
Statyczne metody ............................................................................................. 493
Podsumowanie ...................................................................................................... 497

Rozdzia 10. Funkcje operatorowe jeszcze jeden dobry pomys

499

Przecianie operatorw ......................................................................................... 501


Ograniczenia w przecianiu operatorw .................................................................. 510
Ktre operatory nie mog by poddane przecianiu? .......................................... 510
Ograniczenia typw wartoci zwracanych przez funkcje operatorowe...................... 512

Spis treci

Ograniczenia liczby parametrw funkcji operatorowych ......................................... 514


Ograniczenia wynikajce z priorytetu operatorw ................................................. 515
Przecione operatory jako komponenty skadowe klas ............................................. 516
Zastpowanie funkcji globalnej metod nalec do klasy ................................... 516
Zastosowanie komponentw klas w operacjach acuchowych ............................. 519
Zastosowanie sowa kluczowego const............................................................... 521
Analiza przedmiotowa uamki zwyke.................................................................... 523
Mieszane typy danych jako parametry ...................................................................... 533
Funkcje zaprzyjanione friend ............................................................................... 541
Podsumowanie ...................................................................................................... 556

Rozdzia 11. Konstruktory i destruktory potencjalne problemy

557

Wicej o przekazywaniu obiektw poprzez warto...................................................... 559


Przecianie operatorw w klasach nie bdcych klasami numerycznymi .................... 566
Klasa String ..................................................................................................... 567
Dynamiczne zarzdzanie pamici na stercie ...................................................... 569
Ochrona danych na stercie nalecych do obiektu od strony kodu klienta.............. 574
Przeciony operator konkatenacji acuchw znakowych ..................................... 574
Zapobieganie wyciekom pamici ........................................................................ 577
Ochrona integralnoci programu......................................................................... 578
Jak std przej tam? ................................................................................. 583
Wicej o konstruowaniu kopii obiektw .................................................................... 585
Sposb na zachowanie integralnoci programu ................................................... 585
Semantyka referencji i semantyka wartoci......................................................... 590
Konstruktor kopiujcy definiowany przez programist........................................... 592
Zwrot poprzez warto....................................................................................... 597
Ograniczenia skutecznoci konstruktorw kopiujcych ......................................... 600
Przecienie operatora przypisania .......................................................................... 601
Problem z dodan przez kompilator obsug operatora przypisania ....................... 602
Przecienie przypisania wersja pierwsza (z wyciekiem pamici) ....................... 603
Przecienie przypisania wersja nastpna (samoprzypisanie) ........................... 604
Przecienie przypisania jeszcze jedna wersja (wyraenia acuchowe)................... 605
Pierwszy rodek zapobiegawczy wicej przeciania ........................................ 610
Drugi rodek zapobiegawczy zwrot wartoci poprzez referencj......................... 611
Rozwaania praktyczne jak chcielibymy to zaimplementowa? ............................. 612
Podsumowanie ...................................................................................................... 616

Cz III Programowanie obiektowe przy wykorzystaniu agregacji


oraz dziedziczenia
Rozdzia 12. Klasy zoone puapki i zalety

619
621

Wykorzystywanie obiektw jako danych skadowych .................................................. 623


Skadnia C++ dotyczca zoenia klas ................................................................ 625
Dostp do danych skadowych komponentw klasy ............................................. 626
Dostp do danych skadowych parametrw metody ............................................. 629
Inicjalizacja obiektw zoonych............................................................................... 630
Wykorzystanie domylnych konstruktorw komponentw...................................... 632
Wykorzystanie listy inicjalizujcej skadowe ......................................................... 638
Dane skadowe ze specjalnymi waciwociami ........................................................ 644
Stae dane skadowe ........................................................................................ 645
Dane skadowe okrelone przez referencje ......................................................... 646

10

C++. Inynieria programowania


Wykorzystywanie obiektw w charakterze danych skadowych ich wasnej klasy ..... 649
Wykorzystywanie statycznych danych skadowych
w charakterze skadowych ich wasnych klas.................................................. 651
Klasy kontenerw................................................................................................... 654
Klasy zagniedone........................................................................................... 670
Klasy zaprzyjanione ......................................................................................... 673
Podsumowanie ...................................................................................................... 676

Rozdzia 13. Klasy podobne jak je traktowa?

677

Traktowanie podobnych klas ................................................................................... 679


czenie cech podklas w jednej klasie................................................................ 681
Przekazywanie odpowiedzialnoci za integralno programu do klasy serwera........ 683
Oddzielenie klas dla kadego rodzaju obiektu serwera ......................................... 688
Wykorzystywanie dziedziczenia w jzyku C++ w celu czenia powizanych klas...... 691
Skadnia dziedziczenia w jzyku C++ ....................................................................... 694
Rne tryby dziedziczenia z klasy bazowej ........................................................... 695
Definiowanie oraz wykorzystywanie obiektw klas bazowych oraz klas pochodnych ....699
Dostp do usug klasy bazowej oraz pochodnej ........................................................ 701
Dostp do komponentw bazowych w obiektach klasy pochodnej .............................. 706
Dziedziczenie publiczne ..................................................................................... 706
Dziedziczenie chronione .................................................................................... 711
Dziedziczenie prywatne...................................................................................... 716
Zwikszanie dostpu do skadowych bazowych w klasie pochodnej....................... 718
Domylny tryb dziedziczenia............................................................................... 720
Reguy zakresu widocznoci oraz rozwizywanie nazw przy stosowaniu dziedziczenia ... 722
Przecianie oraz ukrywanie nazw ...................................................................... 725
Wywoywanie metody klasy bazowej ukrytej przez klas pochodn......................... 729
Wykorzystanie dziedziczenia w celu rozwoju programu.......................................... 733
Konstruktory oraz destruktory klas pochodnych ........................................................ 736
Wykorzystanie list inicjalizujcych w konstruktorach klas pochodnych.................... 740
Destruktory w przypadku dziedziczenia ............................................................... 743
Podsumowanie ...................................................................................................... 745

Rozdzia 14. Wybr pomidzy dziedziczeniem a zoeniem

747

Wybr techniki wielokrotnego wykorzystywania kodu ................................................. 749


Przykad relacji typu klient-serwer pomidzy klasami ............................................ 749
Ponowne wykorzystanie kodu poprzez ludzk inteligencj
po prostu zrb to jeszcze raz.................................................................... 753
Ponowne uycie kodu poprzez kupowanie usug................................................... 755
Ponowne wykorzystanie kodu poprzez dziedziczenie............................................. 759
Dziedziczenie wraz z ponownym zdefiniowaniem funkcji........................................ 764
Plusy i minusy dziedziczenia oraz zoenia .......................................................... 766
Jzyk UML ............................................................................................................. 768
Cele stosowania jzyka UML.............................................................................. 768
Podstawy UML Notacja klas .......................................................................... 772
Podstawy UML notacja relacji ........................................................................ 773
Podstawy UML notacja dla agregacji oraz uoglnienia...................................... 774
Podstawy UML notacja krotnoci ................................................................... 776
Studium przypadku wypoyczalnia filmw ............................................................. 778
Klasy oraz ich skojarzenia ................................................................................. 779

Spis treci
Widoczno klasy oraz podzia odpowiedzialnoci ..................................................... 796
Widoczno klas oraz relacje pomidzy klasami .................................................. 797
Przekazywanie odpowiedzialnoci do klas serwera............................................... 799
Stosowanie dziedziczenia .................................................................................. 801
Podsumowanie ...................................................................................................... 804

Cz IV Zaawansowane wykorzystanie jzyka C++


Rozdzia 15. Funkcje wirtualne oraz inne zaawansowane sposoby
wykorzystania dziedziczenia

805
807

Konwersje pomidzy klasami niepowizanymi........................................................... 809


cisa oraz saba kontrola typw ........................................................................ 812
Konstruktory konwertujce ................................................................................ 813
Rzutowania pomidzy wskanikami (lub referencjami) .......................................... 815
Operatory konwersji .......................................................................................... 816
Konwersje pomidzy klasami powizanymi poprzez dziedziczenie ............................... 817
Konwersje bezpieczne oraz niebezpieczne .......................................................... 818
Konwersje wskanikw oraz referencji do obiektw.............................................. 824
Konwersje wskanikw oraz referencji wystpujcych w charakterze argumentw... 833
Funkcje wirtualne kolejny nowy pomys ................................................................ 840
Wizanie dynamiczne podejcie tradycyjne ..................................................... 843
Wizanie dynamiczne podejcie obiektowe ..................................................... 852
Wizanie dynamiczne funkcje wirtualne .......................................................... 861
Wizanie dynamiczne oraz statyczne .................................................................. 865
Funkcje czysto wirtualne.................................................................................... 869
Funkcje wirtualne destruktory ........................................................................ 873
Wielodziedziczenie kilka klas bazowych ............................................................... 875
Wielodziedziczenie reguy dostpu ................................................................. 877
Konwersje pomidzy klasami ............................................................................. 878
Wielodziedziczenie konstruktory oraz destruktory ............................................ 880
Wielodziedziczenie niejednoznacznoci........................................................... 881
Wielodziedziczenie grafy skierowane .............................................................. 884
Czy wielodziedziczenie jest przydatne?................................................................ 885
Podsumowanie ...................................................................................................... 886

Rozdzia 16. Zaawansowane wykorzystanie przeciania operatorw

889

Przecianie operatorw krtki wstp .................................................................. 890


Operatory jednoargumentowe.................................................................................. 898
Operatory inkrementacji oraz dekrementacji ........................................................ 899
Przyrostkowe operatory przecione ................................................................... 907
Operatory konwersji .......................................................................................... 910
Operatory indeksowania oraz wywoania funkcji ........................................................ 918
Operator indeksowania ..................................................................................... 918
Operator wywoania funkcji ................................................................................ 927
Operatory wejcia-wyjcia ....................................................................................... 933
Przecianie operatora >> ................................................................................. 933
Przecianie operatora << ................................................................................. 937
Podsumowanie ...................................................................................................... 939

11

12

C++. Inynieria programowania


Rozdzia 17. Szablony jeszcze jedno narzdzie projektowania

941

Prosty przykad projektowania klas przeznaczonych do wielokrotnego wykorzystania......... 942


Skadnia definicji klasy szablonu ............................................................................. 951
Specyfikacja klasy szablonu .............................................................................. 952
Konkretyzacja szablonu ..................................................................................... 953
Implementacja funkcji szablonu ......................................................................... 955
Szablony zagniedone...................................................................................... 962
Klasy szablonw z wieloma parametrami ................................................................. 963
Kilka parametrw okrelajcych typ.................................................................... 963
Szablony z parametrami okrelonymi za pomoc staego wyraenia ...................... 967
Zwizki pomidzy konkretyzacjami klas szablonw .................................................... 970
Zaprzyjanione klasy szablonw ......................................................................... 970
Zagniedone klasy szablonw........................................................................... 974
Szablony ze skadowymi statycznymi .................................................................. 977
Specjalizacje szablonw ......................................................................................... 979
Funkcje szablonowe ............................................................................................... 983
Podsumowanie ...................................................................................................... 985

Rozdzia 18. Programowanie przy uyciu wyjtkw

987

Prosty przykad obsugi wyjtkw ............................................................................. 988


Skadnia wyjtkw w jzyku C++.............................................................................. 995
Generowanie wyjtkw ...................................................................................... 997
Przechwytywanie wyjtkw ................................................................................. 998
Deklaracja wyjtkw........................................................................................ 1005
Przekazywanie wyjtkw .................................................................................. 1007
Wykorzystywanie wyjtkw z obiektami................................................................... 1011
Skadnia generowania, deklaracji oraz przechwytywania obiektw ....................... 1011
Wykorzystywanie dziedziczenia podczas stosowania wyjtkw ............................ 1015
Wyjtki zdefiniowane w bibliotece standardowej ................................................ 1020
Operatory rzutowania............................................................................................ 1021
Operator static_cast ....................................................................................... 1022
Operator reinterpret_cast ................................................................................ 1026
Operator const_cast ....................................................................................... 1026
Operator dynamic_cast ................................................................................... 1029
Operator typeid............................................................................................... 1032
Podsumowanie .................................................................................................... 1033

Rozdzia 19. Czego nauczye si dotychczas?

1035

C++ jako tradycyjny jzyk programowania............................................................... 1036


Wbudowane typy danych jzyka C++................................................................. 1036
Wyraenia jzyka C++ ..................................................................................... 1038
Przepyw kontroli w programie C++ ................................................................... 1040
C++ jako jzyk moduowy...................................................................................... 1041
Typy agregacyjne jzyka C++ tablice............................................................. 1042
Typy agregacyjne jzyka C++ struktury, unie, wyliczenia ................................. 1043
Funkcje C++ jako narzdzia modularyzacji......................................................... 1044
Funkcje C++ przekazywanie parametrw....................................................... 1046
Zakres widocznoci oraz klasy pamici w jzyku C++......................................... 1048
C++ jako jzyk obiektowy...................................................................................... 1049
Klasy jzyka C++ ............................................................................................ 1050
Konstruktory, destruktory oraz operatory przecione......................................... 1051

Spis treci
Skadanie klas oraz dziedziczenie .................................................................... 1052
Funkcje wirtualne oraz klasy abstrakcyjne ......................................................... 1054
Szablony ........................................................................................................ 1055
Wyjtki........................................................................................................... 1056
Jzyk C++ a konkurencja ...................................................................................... 1058
Jzyk C++ a starsze jzyki programowania ........................................................ 1058
Jzyk C++ a Visual Basic................................................................................. 1058
Jzyk C++ a C ................................................................................................ 1059
Jzyk C++ a Java ............................................................................................ 1060
Podsumowanie .................................................................................................... 1062

Dodatki
Skorowidz

1063
1065

13

Konstruktory i destruktory
potencjalne problemy
Zagadnienia omwione w tym rozdziale:
n

Wicej o przekazywaniu obiektw poprzez warto.

Przecianie operatorw w klasach nie bdcych klasami numerycznymi.

Wicej o konstruowaniu kopii obiektw.

Przecienie operatora przypisania.

Rozwaania praktyczne jak chcielibymy to zaimplementowa?

Podsumowanie.

Funkcje operatorowe dokonujce przecienia operatorw przydaj nowego impulsu programowaniu obiektowemu. Okazao si, e oto zamiast koncentrowa si na czeniu w jedn,
logicznie powizan cao danych i operacji w oparciu o pewne wsplne koncepcje, zajmujemy si rozwaaniami w kategoriach estetycznych i zagadnieniami rwnego traktowania
wbudowanych, elementarnych typw danych oraz typw definiowanych przez programist
w programach pisanych w C++.
Ten rozdzia stanowi bezporedni kontynuacj poprzedniego. W rozdziale 10. Funkcje operatorowe jeszcze jeden dobry pomys omawiaem zagadnienia odnoszce si do projektowania klas typu numerycznego na przykadzie klas  (liczba zespolona) oraz 
 
(uamek zwyky). Obiekty stanowice zmienne (instancje) tych klas s penoprawnymi obiektami, rzec mona z prawdziwego zdarzenia. Stosuj si do nich wszystkie zagadnienia odnoszce si do klas i obiektw deklaracja klasy, kontrola dostpu do skadnikw klasy,
projektowanie metod, definiowanie obiektw i przesyanie komunikatw do obiektw.
Typy danych definiowanych przez programist (klasy) omawiane poprzednio s danymi numerycznymi. Nawet jeli maj wewntrzn struktur bardziej skomplikowan ni elementarne typy
liczb cakowitych czy liczb zmiennoprzecinkowych, obiekty takich klas mog by w kodzie

558

Cz II n Programowanie obiektowe w C++


klienta obsugiwane w sposb podobny do liczb cakowitych i liczb zmiennoprzecinkowych.
W kodzie klienta obiekty takich typw mog by dodawane, mnoone, porwnywane itp.
Popatrzmy uwanie na sposb wyartykuowania ostatniej myli (w ostatnich dwch zdaniach).
Okrelenie obiekty takich typw mog odnosi si oczywicie do typw zdefiniowanych
przez programist. Co natomiast oznacza podobny do liczb cakowitych czy liczb zmiennoprzecinkowych? Skoro te dane porwnujemy z typami zdefiniowanymi przez programist, mamy tu na myli typy wartoci cakowitych i zmiennoprzecinkowych, a nie same
zmienne tyche typw. Lepiej zapewne byoby tu uy sformuowania wbudowane typy
danych. Co oznacza obiekty takich klas mog by w kodzie klienta obsugiwane? Tu
chyba take chodzi o typy zdefiniowane przez programist? Niezupenie, poniewa w tym
zdaniu mwi si o obsudze po stronie kodu klienta. Kod klienta nie obsuguje typw zdefiniowanych przez programist, lecz obiekty typu zdefiniowanego przez programist. To zmienne okrelonego typu (instancje, obiekty) s dodawane, mnoone, porwnywane, itp. Autor
podkrela tu wyranie ten szczeg, poniewa wiesz ju o klasach i obiektach wystarczajco duo, by wyczuwa brak precyzji w niektrych sformuowaniach w dyskusji o programowaniu obiektowym i by samemu unika, w miar moliwoci, takich nieprecyzyjnych sformuowa.
Innymi sowy, obiekty klas (typw) numerycznych, zdefiniowanych przez programist, mog
po stronie kodu klienta by obsugiwane podobnie do zmiennych elementarnych typw wbudowanych. Z tego powodu obsuga przeciania operatorw wobec takich klas zdecydowanie
ma sens. Zasada C++, by umoliwia traktowanie zmiennych typw elementarnych i obiektw typw zdefiniowanych przez programist w jednakowy sposb dziaa prawidowo
wobec tych klas. W tym rozdziale autor zamierza omwi przecianie operatorw wobec
takich klas, ktrych obiekty nie mog by dodawane, mnoone, odejmowane ani dzielone.
Na przykad do obsugi acuchw znakw w pamici mona utworzy klas
 . Z racji nienumerycznego charakteru takich klas (z samej ich natury) funkcje operatorowe dokonujce przecienia operatorw wobec tych klas wygldaj sztucznie i nienaturalnie. Na przykad moemy dokona przecienia operatora dodawania ( w taki sposb, by umoliwia
konkatenacj dwch obiektw klasy
  (dodanie drugiego acucha znakw do koca
pierwszego) lub np. przecienia operatora numerycznej rwnoci ( w taki sposb, by
umoliwia porwnanie dwch acuchw znakw reprezentowanych przez obiekty klasy

 . I to wyglda do rozsdnie. Z drugiej jednak strony, trudno wymyli jakkolwiek
rozsdn interpretacj dla operatorw mnoenia czy dzielenia wobec obiektw klasy
 .
Niemniej jednak funkcje operatorowe dokonujce przeciania operatorw C++1 dla klas nienumerycznych s popularne i powinno si wiedzie, jak z nimi postpowa.
Wan rnic pomidzy klasami numerycznymi a nienumerycznymi jest to, e w klasach
nienumerycznych mog wystpowa znaczce rnice w objtoci danych, ktrymi mog
posugiwa si obiekty tej samej klasy. Obiekty klas numerycznych zawsze zajmuj tak
sam ilo miejsca w pamici. Na przykad obiekty klasy 
  zawieraj zawsze dwa
pola danych licznik i mianownik.
Natomiast w przypadku klasy
  objto tekstu, ktry moe by przechowywany w pojedynczym obiekcie tej klasy, moe by inna ni objto tekstu przechowywana w innym
obiekcie tej samej klasy. Jeli klasa rezerwuje dla kadego obiektu tak sam (zbyt du)

Przypomnijmy: pierwotnie, z natury arytmetycznych przyp. tum.

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

559

ilo pamici, mamy do czynienia w programie z dwoma niekorzystnymi, a skrajnymi zjawiskami marnowaniem pamici (gdy rzeczywista wielko tekstu jest mniejsza ni ilo
zarezerwowanej pamici) albo z przepenieniem pamici (gdy rzeczywista wielko tekstu
okae si wiksza ni ilo zarezerwowanej pamici). Te dwa niebezpieczestwa czyhaj stale
i w ktr z tych dwu puapek zawsze, prdzej czy pniej, wpadn ci projektanci klas, ktrzy rezerwuj t sam ilo pamici dla kadego takiego obiektu.
C++ rozwizuje ten problem poprzez przyporzdkowanie dla kadego obiektu tej samej iloci
pamici (na stercie albo na stosie) zgodnie ze specyfikacj klasy, a nastpnie ewentualne dodanie, w miar potrzeby, dodatkowej pamici na stercie2. Taka dodatkowa ilo pamici na
stercie zmienia si i moe by zupenie inna dla poszczeglnych obiektw. Nawet dla jednego,
konkretnego obiektu podczas jego ycia ta ilo dodatkowej pamici moe si zmienia. Na
przykad jeli do obiektu klasy
  zawierajcego jaki tekst dodawany jest nastpny
acuch znakw (konkatenacja), taki obiekt moe powikszy swoj pami, by pomieci
nowy, duszy tekst.
Dynamiczne zarzdzanie pamici na stercie obejmuje zastosowanie konstruktorw i destruktorw. Ich nieumiejtne, niezrczne stosowanie moe negatywnie wpyn na efektywno
dziaania programu. Co gorsza, moe to spowodowa utrat danych przechowywanych
w pamici i utrat integralnoci programu, co jest zjawiskiem charakterystycznym dla C++,
nieznanym w innych jzykach programowania. Kady programista pracujcy w C++ powinien by wiadom tych zagroe. To z tego powodu zagadnienia te zostay wczone do tytuu
niniejszego rozdziau, mimo e ten rozdzia stanowi w istocie kontynuacj rozwaa o funkcjach operatorowych dokonujcych przecienia operatorw.
Dla uproszczenia dyskusji niezbdne podstawowe koncepcje zostan tu wprowadzone w oparciu o klas 
  (o staej wielkoci obiektw), znan z rozdziau 10. W tym rozdziale te
koncepcje zostan zastosowane w odniesieniu do klasy
  z dynamicznym zarzdzaniem
pamici na stercie. W rezultacie twoja intuicja programistyczna wzbogaci si o wyczucie relacji
pomidzy instancjami obiektw po stronie kodu klienta. Jak si przekonasz, relacje pomidzy
obiektami oka si inne ni relacje pomidzy zmiennymi typw elementarnych, pomimo
wysikw, by takie zmienne i obiekty byy traktowane tak samo. Innymi sowy, powiniene
przygotowa si na due niespodzianki.
Lepiej nie pomija materiau zawartego w niniejszym rozdziale. Zagroenia zwizane z zastosowaniem i rol konstruktorw i destruktorw w C++ s to niebezpieczestwa zdecydowanie realne i naley wiedzie, jak si przed nimi broni (bronic przy okazji swojego szefa
i swoich uytkownikw).

Wicej o przekazywaniu obiektw poprzez warto


Wczeniej, w rozdziale 7. (Programowanie w C++ z zastosowaniem funkcji) przytaczano
argumenty przeciwko przekazywaniu obiektw do funkcji jako parametrw poprzez warto albo poprzez wskanik do obiektu, natomiast zachcano, by zamiast tego przekazywa
obiekty jako parametry poprzez referencje.
2

W sposb dynamiczny przyp. tum.

560

Cz II n Programowanie obiektowe w C++


Autor wyjania tam, e przekazanie parametru poprzez referencj jest prawie rwnie proste,
jak przekazanie go poprzez warto, jest jednake szybsze w przypadku tych parametrw
wejciowych, ktre nie zostan zmodyfikowane przez dan funkcj. Przekazywanie poprzez
referencj jest rwnie szybkie, jak przekazywanie poprzez wskanik, ale skadnia odnoszca
si do parametrw przekazywanych poprzez referencj jest znacznie prostsza dla parametrw wyjciowych, ktre zostaj zmodyfikowane przez dan funkcj w trakcie jej wykonania.
Zaznaczono tam rwnie, e skadnia przy przekazywaniu parametrw poprzez referencje
jest dokadnie taka sama i dla parametrw wejciowych, i dla parametrw wyjciowych
funkcji. Z tego te powodu autor sugerowa stosowanie sowa kluczowego (modyfikatora)  

dla parametrw wejciowych w celu wskazania, e te parametry nie zostan zmodyfikowane


w wyniku wykonania danej funkcji. Jeli nie stosujemy adnych takich sugestii, powinno to
w sposb nie wzbudzajcy wtpliwoci wskazywa, e te parametry bd zmienione w trakcie wykonania danej funkcji.
Oprcz tego autor przytacza argumenty przeciwko zwracaniu obiektw z funkcji poprzez
warto, jeli tylko nie jest to niezbdne w celu przesania innych komunikatw do takiego
zwrconego obiektu (skadnia acuchowa w wyraeniach).
Przy takim podejciu przekazywanie parametrw poprzez warto powinno by ograniczone
do przekazywania parametrw wejciowych funkcji, jeli s one typw elementarnych, i zwrotu
z funkcji wartoci tyche typw elementarnych. Dlaczego parametry wejciowe typw wbudowanych s akceptowalne? Przekazywanie ich poprzez wskanik zwikszy stopie zoonoci i moe wprowadzi w bd osob czytajc kod, sugerujc, e taki parametr moe zosta
zmodyfikowany podczas wykonania funkcji. Przekazanie ich poprzez referencje (z modyfikatorem  
) nie jest trudne, ale zwiksza nieco stopie zoonoci kodu. Skoro takie parametry s niewielkie, przekazywanie ich poprzez referencje nie zwikszy w najmniejszym
stopniu szybkoci dziaania programu. Z wymienionych powodw najprostszy sposb przekazywania parametrw (poprzez warto) jest stosowny dla typw wbudowanych.
W trakcie ostatniego rozdziau poznalimy wystarczajco techniki programowania, umoliwiajce nam nie tylko omwienie wad i zalet rnych trybw przekazywania parametrw
do funkcji i z funkcji, lecz take umoliwiajcych nam przeledzenie kolejnoci wywoa
funkcji.
Oprcz tego przy rnych okazjach autor podkrela, e inicjowanie i przypisanie, nawet jeli w obu przypadkach nastpuje uycie tego samego symbolu  , s rnie traktowane . W tym
podrozdziale przy uyciu wydrukw komunikatw diagnostycznych zostan zademonstrowane
te rnice.
Oba zagadnienia zostan zademonstrowane za pomoc przykadowego kodu z listingu 11.1,
zawierajcego uproszczon (i zmodyfikowan) wersj klasy 
  z poprzedniego rozdziau
oraz funkcj testujc

 .

Listing 11.1. Przykad przekazania obiektw jako parametrw funkcji poprzez warto

 
   

       
         

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

561

!"
    #$   #%  &  &  ' &  (   )
  #   # 
 *  
 

+ !&     &  &  " +



+ +



 ,
       -  &  &  & (.
  #   # 
 

+ !& &   &  &  & (. " +



+ +




,
    #      -     
  #   # 
 

+ !&     # " +



+ +



 ,
/     & 
  

+ !&  0   &  " +



+ +



 ,
1      2      3       
      
,   &  1&( & 
    ""    
  

+ +



++

 ,
    ""      
 1  ## $   # %   ,
  # %
1 
$   # *%  # * ,  4 &
1 
$   # *   # * ,  ! ! !5   
   #   #   (0&   '  &
  6#     (   
1   
 #  *   (( !0 ( .  0& (
   #  *  ,
 #  7    #  ,   &   
     2      3      
     73 2 37 73  ,
 
    %8  !9:  
 


 #  2 !
     

+ 2+ !     

+ #+    
 




  $
,

Spord wszystkich funkcji odnoszcych si uprzednio do klasy 


  pozostawiono tu
jedynie funkcje   ,  oraz 
. Poza tym zwr uwag, e funkcja
dokonujca przecienia operatora dodawania, 
, nie jest w tym przypadku metod, lecz funkcj zaprzyjanion kategorii   . Z tego powodu na pocztku tego podrozdziau (i dalej) w odniesieniu do klasy 
  uyto stwierdzenia: wszystkich funkcji
odnoszcych si uprzednio do klasy, a nie wszystkich metod nalecych do klasy. Autor
postpi tak, cho chciaby jeszcze raz zaakcentowa, e zaprzyjanione funkcje kategorii
   z punktu widzenia wszelkich zamiarw i zastosowa s jak metody nalece do klasy. Funkcja zaprzyjaniona jest implementowana w tym samym pliku, co wszystkie metody,
ma takie same prawa dostpu do prywatnych skadnikw klasy, jak wszystkie metody, jest
cakowicie bezuyteczna wobec obiektw klasy innej ni jej klasa zaprzyjaniona, w tym
przypadku klasa 
  jak wszystkie inne metody. Rnica polega jedynie na skadni
stosowanej przy wywoaniu tej funkcji. Tylko tu funkcja zaprzyjaniona rni si od metod,

562

Cz II n Programowanie obiektowe w C++


ale w przypadku funkcji operatorowych, dokonujcych przeciania operatorw, skadnia
operatorowa jest dokadnie taka sama dla funkcji zaprzyjanionych i dla metod nalecych
do klasy.
W obrbie oglnego (dwuargumentowego) konstruktora klasy 
  dodano wydruk komunikatu o charakterze diagnostycznym. Ten wydruk komunikatu powinien nastpowa za
kadym razem, gdy obiekt klasy 
  jest tworzony i inicjowany przez ten konstruktor
na pocztku kodu funkcji   i we wntrzu kodu funkcji operatorowej 
.
  ""    #$   #%   )  )

 #   #   (  
 *  
 

+ !&     &  &  " +



+ +




,

Dodano tu take konstruktor kopiujcy z wydrukiem komunikatu diagnostycznego. Ten komunikat diagnostyczny zostanie wyprowadzony na ekran zawsze, gdy obiekt klasy 
 
bdzie inicjowany za pomoc kopiowania pl danych innego obiektu klasy 
 . Takie
kopiowanie obiektu na przykad nastpi wtedy, gdy parametry bd przekazywane poprzez
warto do funkcji 
 lub gdy bdzie nastpowa zwrot wartoci z funkcji (zwrot
obiektu poprzez warto).
  ""       -  &  &  & (.

 #   #   & (   
 

+ !& &   &  &  & (. " +



+ +




,

Ten konstruktor kopiujcy jest wywoywany, gdy argumenty klasy (typu) 


  s poprzez
warto przekazywane do zaprzyjanionej funkcji operatorowej   
. Wbrew
mylnemu pierwszemu wraeniu, ten konstruktor kopiujcy nie zostanie wywoany, gdy funkcja 
 bdzie zwraca obiekt poprzez warto, poniewa przed powrotem z tej funkcji operatorowej nastpi wywoanie dwuargumentowego konstruktora oglnego.
W klasie 
  destruktor nie ma do wykonania odpowiedzialnego zadania i zosta dodany
do specyfikacji tej klasy tylko w celu uatwienia wyprowadzania komunikatw diagnostycznych; destruktor zgosi si poprzez wydruk komunikatu diagnostycznego zawsze, gdy obiekt
klasy 
  bdzie usuwany.
Najciekawsz funkcj jest tu funkcja dokonujca przecienia operatora przypisania (). Jej
praca polega na kopiowaniu pl danych jednego obiektu klasy 
  na pola danych innego obiektu klasy 
 . Czym jej dziaanie rni si od dziaania konstruktora kopiujcego? W tym stadium moemy stwierdzi, e niczym, cho inny jest typ wartoci zwracanej.
Konstruktor kopiujcy, jak to konstruktor, nie moe mie adnego typu wartoci zwracanej,
natomiast metoda operatorowa, jak wikszo metod, musi mie jaki typ wartoci zwracanej.
Dla uproszczenia przyjto tu ten typ wartoci zwracanej jako  .
    ""   #      -  (  

 #   #   &   
 

+ !&     # " +



+ +




,

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

563

Poddany tu przecieniu operator przypisania ( jest operatorem dwuargumentowym (binarnym). Skd to wiadomo? Po pierwsze, funkcja operatorowa ma jako jeden parametr obiekt
klasy 
 , a nie jest to funkcja zaprzyjaniona, lecz metoda. Skoro tak, jak kada metoda
z jednym parametrem obiektowym, dziaa na dwch argumentach. Jednym, niejawnym argumentem jest obiekt docelowy komunikatu (widziany przez metod jako wasny obiekt), drugim, jawnym argumentem jest obiekt parametr. Drugim wyjanieniem jest skadnia stosowania operatora przypisania (po stronie kodu klienta). Operator dwuargumentowy jest zawsze
wstawiany pomidzy pierwszy a drugi operand. Gdy dodaje si dwa operandy, kolejno
jest nastpujca: pierwszy operand, operator, drugi operand (np. ). Podobnie jest, gdy
dokonuje si przypisania. I tu kolejno jest nastpujca: pierwszy operand, operator, drugi
operand (np.   ). Odnoszc to teraz do skadni wywoania funkcji, zauwaamy, e
obiekt jest docelowym obiektem komunikatu. W podanej funkcji operatorowej, dokonujcej
przecienia operatora przypisania, pola  (licznik uamka) oraz   (mianownik uamka)
nale do docelowego obiektu komunikatu, . Obiekt  jest biecym argumentem danego
wywoania funkcji operatorowej. W podanej funkcji operatorowej, dokonujcej przecienia
operatora przypisania, pola   oraz   nale do biecego argumentu metody, .
Skoro tak, to operator ( ) przypisania odpowiada wywoaniu funkcji operatorowej o nastpujcej skadni: 
.
Poniewa ta metoda ma typ wartoci zwracanej   (nie zwraca adnej wartoci), taka
funkcja operatorowa nie umoliwia obsugi wyrae acuchowych po stronie kodu klienta
(np. ). Takie wyraenie jest przez kompilator interpretowane jako .
Oznacza to, e warto zwracana przez operator przypisania (  ), to znaczy poprzez
wywoanie funkcji operatorowej 
, musiaaby zosta wykorzystana jako argument kolejnego wywoania tej samej funkcji operatorowej: 

.
Aby takie wyraenie byo poprawne i zostao poprawnie zinterpretowane, operator przypisania powinien zwraca jako warto obiekt (tu klasy 
 ). Skoro funkcja zostaa zaprojektowana tak, e jej typ wartoci zwracanej jest  , wyraenie acuchowe po stronie
kodu klienta zostanie przez kompilator zasygnalizowane jako bd skadniowy. Przy pierwszym spojrzeniu na przecianie operatora przypisania to nie jest istotne. Wyraenia acuchowe bd stosowane pniej, w dalszej czci niniejszego rozdziau.
Wydruk wyjciowy programu z listingu 11.1 zosta pokazany na rysunku 11.1. Pierwsze trzy
wydrukowane komunikaty utworzony pochodz od konstruktorw w rezultacie utworzenia
i zainicjowania trzech obiektw klasy 
  w obrbie funkcji  . Dwa komunikaty
o kopiowaniu, skopiowany, pochodz z wntrza funkcji przeciajcej operator dodawania

. Kolejny komunikat o utworzeniu obiektu,
 !, wynika z wywoania konstruktora klasy 
  wewntrz ciaa funkcji 
.
Wszystkie te wywoania konstruktorw maj miejsce na pocztku wykonania funkcji. Pniej nastpuje seria zdarze, gdy wykonanie kodu funkcji dochodzi do koca (tj. do nawiasu
klamrowego zamykajcego kod ciaa funkcji), a lokalne i tymczasowe obiekty s usuwane.
Pierwsze dwa komunikaty destruktora usunity) nastpuj wtedy, gdy dwie lokalne kopie
biecych argumentw (obiekty uamki 3/2 oraz 1/4) s usuwane i dla tych dwch obiektw
nastpuje wywoanie destruktorw. Ten obiekt, ktry zawiera sum dwch argumentw, nie
moe zosta usunity, zanim nie zostanie uyty w operacji (tzn. przez operator) przypisania.
Kolejny komunikat, przypisany, pochodzi z wywoania funkcji dokonujcej przecienia
operatora przypisania 
. Wreszcie komunikat  "
! pochodzi od destruktora
tego obiektu, ktry zosta utworzony w obrbie ciaa funkcji 
. Ostatnie trzy komunikaty  "
! pochodz od tych destruktorw, ktre s wywoywane, gdy wykonanie

564

Cz II n Programowanie obiektowe w C++

Rysunek 11.1.
Wydruk wyjciowy
programu z listingu 11.1

programu dochodzi do nawiasu klamrowego koczcego kod funkcji   i usuwane s


obiekty , , . Skoro konstruktor kopiujcy nie jest wywoywany, komunikat #  !
nie pojawia si na wydruku wyjciowym.
Ta sekwencja zdarze rozegra si zupenie inaczej, jeli do interfejsu funkcji przeciajcej
operator, 
, dodane zostan dwa znaki $ (ang. ampersand) i parametry bd
przekazywane poprzez referencje.
     2      -3      -  1(
     73 2 37 73  ,

Wymaganie zachowania spjnoci pomidzy rnymi czciami kodu w C++ jest postulatem rygorystycznym. W tym przypadku zmieniono interfejs (prototyp funkcji) i analogicznie
zmodyfikowano deklaracj teje funkcji w obrbie specyfikacji klasy. (Take tym razem nie
ma tu znaczenia, czy jest to metoda, czy te funkcja zaprzyjaniona kategorii   ). W tym
przypadku niezapewnienie spjnoci rnych czci kodu programu nie jest miertelnym zagroeniem. W razie czego kompilator powinien nas ostrzec, e kod taki zawiera bdy skadniowe.
Wydruk wyjciowy, powstay w wyniku wykonania programu z listingu 11.1 po zmodyfikowaniu funkcji 
 tak jak poprzednio, pokazano na rysunku 11.2. Jak wida, cztery
wywoania funkcji przepady. Dwa obiekty-parametry nie s tworzone (brak komunikatu konstruktora) i odpowiednio dwa obiekty-parametry nie s usuwane (brak komunikatu destruktora).
Rysunek 11.2.
Wydruk wyjciowy programu
z listingu 11.2 przy przekazaniu
parametrw poprzez referencje

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

565

Unikaj przekazywania obiektw jako parametrw do funkcji poprzez warto. Powoduje


to nadmiarowe (niekonieczne) wywoania funkcji. Przekazuj obiekty do funkcji poprzez
referencje, poprzedzajc je w prototypie funkcji, na licie argumentw modyfikatorami
  (jeli tylko ma to zastosowanie).

Teraz zademonstrujmy rnic pomidzy inicjowaniem a przypisaniem. Na listingu 11.1


zmienna  podlega dziaaniu operatora przypisania w wyraeniu  . Skd wiadomo, e jest to przypisanie, a nie zainicjowanie? Poniewa po lewej stronie nazwy zmiennej
 nie ma nazwy typu. Typ zmiennej  zosta okrelony wczeniej, na pocztku kodu funkcji
 . Dla kontrastu, ta wersja funkcji   deklaruje i natychmiast inicjuje obiekt  jako
sum obiektw oraz  zamiast utworzy obiekt, a pniej przypisa mu warto w dwch
rnych instrukcjach.
 

   %8  !9:   #  2 !
     

+ 2+ !     

+ #+    
 




  $
,

Na rysunku 11.3 pokazano wydruk wyjciowy powstay w wyniku wykonania programu


z listingu 11.1 z zastosowaniem przekazania parametrw poprzez referencje i z uyciem podanego kodu funkcji  . Jak wida, operator przypisania nie jest tu wywoywany. Nie jest tu
wywoywany take konstruktor kopiujcy, co stanowi naturalny skutek przejcia od przekazywania parametrw poprzez warto do przekazania ich poprzez referencje.
Rysunek 11.3.
Wydruk wyjciowy programu
z listingu 11.2 przy przekazaniu
parametrw poprzez referencje
i zastosowaniu inicjowania
obiektw zamiast przypisania

W dalszej czci zastosowana zostanie podobna technika, by zademonstrowa rnic pomidzy zainicjowaniem a przypisaniem na przykadzie obiektw klasy
 .
Rozrniaj inicjowanie obiektw od przypisywania wartoci obiektom. Przy inicjowaniu
obiektw wywoywany jest konstruktor, a zastosowanie operatora przypisania zostaje
pominite. Podczas operacji przypisania stosuje si operator przypisania, natomiast
wywoanie konstruktora zostaje pominite.

Niniejsze rozwaania i koncepcje odnoszce si do zasadnoci unikania przekazywania


obiektw jako parametrw poprzez warto oraz rozrniania inicjowania obiektw od przypisywania im wartoci s bardzo wane. Przekonaj si, e jeste w stanie przeczyta kod
klienta i powiedzie: Tu wywoywany jest konstruktor, a tu operator przypisania. Doskonal swoj intuicj, by umoliwiaa ci dokonywanie tego typu analiz kodw.

566

Cz II n Programowanie obiektowe w C++

Przecianie operatorw w klasach


nie bdcych klasami numerycznymi
Jak zaznaczono we wprowadzeniu do tego rozdziau, rozszerzenie dziaania wbudowanych
operatorw tak, by obejmoway klasy numeryczne jest naturalne. Funkcje operatorowe
dokonujce przecienia operatorw wobec takich klas s bardzo podobne do dziaania wbudowanych operatorw. Nieprawidowa interpretacja symboli takich przecionych operatorw i ich znaczenia przez programist piszcego kod klienta bd przez serwisanta kodw
jest mao prawdopodobna. Taka koncepcja, by wartoci elementarnych typw wbudowanych
i obiekty typw zdefiniowanych przez programist byy traktowane jednakowo wydaje
si naturalnym sposobem wprowadzenia prostej i zrozumiaej implementacji.
Operatory mog zosta zastosowane rwnie dobrze wobec obiektw klas niematematycznych,
ale rzeczywiste znaczenie operatora dodawania, odejmowania i innych operatorw moe
by mao intuicyjne. To nieco przypomina histori ikonek i wprowadzania polece w graficznym interfejsie uytkownika.
Na samym pocztku by interfejs skadajcy si z tekstowego wiersza polece (ang. command
line interface) i uytkownik musia wpisywa dugie polecenia z parametrami, przecznikami, opcjami, kluczami itp. Nastpnie wprowadzono menu z list dostpnych opcji. Poprzez
wybr pozycji z menu uytkownik mg wyda polecenie do wykonania bez potrzeby samodzielnego jego wpisywania. Nastpnie do takich menu dodano skrty klawiaturowe (ang.
hot keys). Poprzez nacinicie takiej specjalnej kombinacji klawiszy na klawiaturze uytkownik mg wyda okrelone polecenie wprost, bez potrzeby odrywania rk od klawiatury i poszukiwania odpowiedniej pozycji w menu, a czasem w caej strukturze podmenu. W kolejnym
kroku wprowadzono listwy narzdziowe (ang. toolbars) z graficznymi przyciskami odpowiadajcymi poszczeglnym poleceniom. Wskazawszy taki przycisk polecenia kursorem myszy i kliknwszy przyciskiem myszy, uytkownik mg wyda odpowiednie polecenie bez
potrzeby zapamitywania odpowiadajcej mu kombinacji klawiszy. Ikonki umieszczane na
takich przyciskach polece w sposb jednoznaczny i intuitywny (zrozumiay intuicyjnie) byy
jasne: Open, Close, Cut, Print (Otwrz, Zamknij, Wytnij, Drukuj). Gdy stopniowo do takich
paskw narzdziowych dodawano coraz wicej i wicej ikonek, stopniowo staway si one
coraz mniej intuitywne: New, Paste, Output, Execute, Go (co odpowiada poleceniom Nowy,
Wklej, Wyjcie, Wykonaj, Uruchom itp.).
Aby pomc uytkownikowi w opanowaniu tych ikonek, dodano wskazwki podpowiedzi (ang. tooltips) wywietlane automatycznie po wskazaniu danej ikonki kursorem myszy.
Interfejs uytkownika stopniowo stawa si coraz bardziej skomplikowany, aplikacje wymagay coraz wicej miejsca na dysku, coraz wicej pamici operacyjnej i coraz wicej pracy
ze strony programistw. Z drugiej strony, mimo tych wszelkich uatwie, uytkownicy nie
maj teraz zapewne atwiejszego ycia ni wtedy, gdy wydawali polecenia, posugujc si
kombinacjami klawiszy na klawiaturze (ang. hotkeys). Na podobnej zasadzie rozpoczlimy
od przeciania operatorw wobec klas numerycznych, a teraz zamierzamy zastosowa przecianie operatorw i funkcji operatorowych w odniesieniu do klas nienumerycznych. Bdzie to wymaga od nas opanowania wikszej liczby regu, napisania wikszej iloci kodu

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

567

i zetknicia si z wyszym stopniem zoonoci. Z drugiej strony, kod klienta bdzie stawa
si lepszy dziki zastosowaniu zamiast starego stylu wywoa funkcji nowoczesnego
przeciania operatorw.

Klasa String
Omwi tu popularne zastosowanie funkcji przeciajcej operator w odniesieniu do klas
nienumerycznych zastosowanie operatora dodawania  do konkatenacji acuchw znakw (doczenia jednego acucha znakw do koca drugiego acucha).
Rozwamy klas
  zawierajc dwa pola danych wskanik do dynamicznie rozmieszczanej w pamici tablicy znakowej oraz liczb cakowit wskazujc maksymaln
liczb znakw, traktowanych jako wane dane, ktre mog zosta umieszczone w dynamicznie
przyporzdkowanej pamici na stercie. W istocie standardowa biblioteka C++ Standard
Library zawiera klas
  (ktrej nazwa rozpoczyna si do maej litery, tj.  
 ),
ktra to klasa jest przeznaczona do speniania wikszoci wymaga zwizanych z operowaniem acuchami tekstowymi. To wspaniaa i bardzo przydatna klasa. Ta firmowa klasa jest
znacznie bardziej rozbudowana ni klasa
 , ktra bdzie tu omawiana. Nie mona jednak uy firmowej klasy 
  do naszych celw, poniewa jest zbyt skomplikowana i takie techniczne szczegy odwracayby nasz uwag od waciwej dyskusji o dynamicznym
zarzdzaniu pamici i jego konsekwencjach.
Po stronie kodu klienta obiekty naszej klasy
  mog by tworzone na dwa sposoby
poprzez okrelenie maksymalnej liczby obsugiwanych znakw acucha tekstowego albo poprzez okrelenie bezporednio zawartoci acucha tekstowego obsugiwanego przez
dany obiekt. Okrelenie liczby znakw w acuchu wymaga podania jednego parametru liczby cakowitej. Okrelenie zawartoci acucha tekstowego take wymaga podania jednego
parametru tablicy znakowej. Typy tych parametrw s rne, dlatego parametry te powinny
zosta zastosowane w rnych konstruktorach. Poniewa kady spord tych konstruktorw
ma dokadnie jeden parametr typu innego ni typ wasnej klasy, ktry to parametr zostaje
poddany konwersji na warto obiektu danej klasy, obydwa te konstruktory s konstruktorami
konwersji.
Pierwszy z tych konstruktorw konwersji, ktrego parametrem jest liczba cakowita okrelajca ilo potrzebnej pamici, ktr naley obiektowi przydzieli na stercie, ma domyln
warto argumentu rwn zeru. Jeli obiekt klasy
  zostanie utworzony z wykorzystaniem tej domylnej wartoci (tj. jeli nie wyspecyfikowano adnej wartoci argumentu), domylna wielko pamici przyporzdkowana danemu obiektowi w celu przechowywania
acucha tekstowego zostanie przyjta jako zerowa. W takim przypadku ten pierwszy konstruktor konwersji zostanie uyty w roli konstruktora domylnego (tzn. przy skadni deklaracji
 ).
Drugi spord tych konstruktorw konwersji, z tablic znakow (acuchem znakw) jako
parametrem, nie ma domylnej wartoci argumentu. Przyporzdkowanie mu jakiej wartoci
domylnej byoby do trudne, chyba eby zdecydowa si tu na pusty acuch znakw o zerowej dugoci: %%. Wtedy jednak kompilator mgby mie wtpliwoci z racji niejednoznacznoci przy wywoaniu konstruktora domylnego, tj. w razie deklaracji obiektu w postaci

568

Cz II n Programowanie obiektowe w C++



  . Pytanie sprowadzaoby si do tego, czy chcemy tu wywoa pierwszy z konstruktorw z domyln wartoci zerowej dugoci acucha znakw, czy te drugi spord
konstruktorw z domyln wartoci pustym acuchem znakw (ang. empty string).

Bieca zawarto acucha tekstowego moe by modyfikowana ze strony kodu klienta


poprzez wywoanie metody  ! (zmodyfikuj), pozwalajcej na wyspecyfikowanie
nowego acucha tekstowego, ktry po wywoaniu tej metody ma zawiera docelowy obiekt
komunikatu. Aby uzyska dostp do zawartoci obiektu klasy
 , po stronie kodu klienta
mona posuy si metod  (poka). Ta metoda zwraca wskanik do pamici przydzielonej na stercie dla danego obiektu. Ten wskanik moe zosta uyty w kodzie klienta
do wydrukowania zawartoci tego acucha tekstowego, porwnania jej z innym acuchem
znakw (tekstem) itp. Na listingu 11.2 pokazano przykadowy program zawierajcy implementacj klasy
 .
Listing 11.2. Klasa String z dynamicznym przydziaem pamici na stercie
 &  ;   0 (  & .

 
    
 ; 

 7    0 ( !0 & .
 
!"
;    #$   &  &   )  &  (
;   7   &  &  &  (
/;       ( 0
   1  7     ) ! & (
7         &<&  ( !
, 
; "";   
  #  
 #  =2%>   )   %
1  ##?@AA 3 %   <  0   5
=$> # $ ,    54 5 ) $ B ( CD
; "";   7
  #     5 )E   & 
 #  =2%>    (..  )E (   
1  ##?@AA 3 %   <  0   5
   ,  & ( &    !   
; ""/; 
    ,   0     &<&6
  ; "" 1   =>  F . 0.
  *%      5  !
=*%> # $ ,    & 4 54 &'
7 ; ""       (  & &  CD
    ,
 

;  +G    + 
     !5 !" ;  +G (  + 
;  +?     + 
     !5 !" ;  +?   F '()E <+ 
 

+  # +

  

    ( CD

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

569

 

+  # +

  

    ( CD
   . !0   &'
 1+A    1   ! + 
    "  1+?  (  !  H!H * !0 + 
 

+  # +

  


   +I +    & &
     !5 !"    +I( + 
 

+  # +

  


  $
,

Dynamiczne zarzdzanie pamici na stercie


Pierwszy wiersz w kodzie pierwszego spord dwu konstruktorw konwersji ustawia warto
pola danych  (dugo acucha znakw). Drugi wiersz kodu ustawia warto pola danych

 (wskanik do acucha znakw) poprzez przydzia wymaganej iloci pamici na stercie.
Nastpnie w kodzie sprawdza si, czy operacja przyporzdkowania pamici na stercie zakoczya si powodzeniem i do pierwszego bajta przydzielonej na stercie pamici wpisywany
jest znak zerowy3. Dla wszystkich funkcji bibliotecznych C++ taki acuch znakw jest
acuchem pustym, cho zajmuje w pamici miejsce przeznaczone na ilo znakw okrelon
po stronie kodu klienta.
Jeli kod klienta definiuje obiekt klasy
 , a nie dostarcza adnych argumentw, ten
konstruktor zostaje uyty jako konstruktor domylny, powodujc przydzia na stercie pamici
przeznaczonej na pojedynczy znak. Ten jedyny znak zostaje zainicjowany jako terminator
(&'), sygnalizujc pusty acuch znakw.
Na rysunku 11.4 pokazano schemat stanu pamici na stercie, ledzc krok po kroku wykonanie kolejnych instrukcji wchodzcych w skad tego konstruktora po jego wywoaniu w wyniku nastpujcej instrukcji:
;  :$   :% &'  

Rysunek 11.4.
Schemat obsugi pamici
dla pierwszego konstruktora
konwersji z listingu 11.2

Na rysunku 11.4a pokazano pierwsz faz konstruowania obiektu, a na rysunku 11.4b odpowiednio drug faz. Prostokt przedstawia obiekt
klasy
  zawierajcy dwa pola
danych wskanik 
 i liczb cakowit  . Te pola danych w rzeczywistoci mog
3

J$ to kod terminatora acucha tekstowego przyp. tum.

570

Cz II n Programowanie obiektowe w C++


zajmowa tak sam ilo pamici, ale na schematycznym rysunku wskanik 
 zosta
oznaczony jako mniejszy prostokt, by optycznie zasygnalizowa, e to pole nie zawiera
przetwarzanych danych. Nazwa obiektu
i nazwy jego pl danych 
 oraz  zostay
umieszczone poza prostoktem symbolizujcym sam obiekt.
Cz schematu (A) pokazuje, e po wykonaniu instrukcji     
 pole danych 
zostaje zainicjowane i przyjmuje warto 20 (innymi sowy, zawiera warto 20), natomiast
wskanik 
 pozostaje niezainicjowany (wskazuje przypadkowy adres w pamici, czyli co
mu si podoba). Cz schematu (B) pokazuje, e po wykonaniu pozostaych instrukcji,
wchodzcych w skad kodu ciaa tego konstruktora, przydzielone na stercie miejsce (dla 21
znakw) jest wskazywane poprzez wskanik 
 i pierwszy znak jest ustawiony (ma warto)
jako znak zerowy. Rysowanie takiego schematu dla tak prostego obiektu moe si wydawa
przesad, ale autor radzi, by rysowa takie diagramy dla wszystkich kodw manipulujcych
wskanikami i dynamiczn pamici na stercie. To najlepszy sposb do rozwoju naszej intuicji programistycznej odnonie dynamicznego zarzdzania pamici.
W pierwszym wierszu kodu ciaa drugiego konstruktora konwersji nastpuje pomiar dugoci
acucha znakw wyspecyfikowanego po stronie kodu klienta i zainicjowanie wartoci pola
danych  . Drugi wiersz kodu inicjuje pole danych 
 poprzez przydzia wymaganej iloci
pamici na stercie, ktrej pocztek ma wskazywa wskanik 
, a nastpnie kopiuje acuch
znakw dostarczony po stronie kodu klienta do przydzielonej na stercie pamici. Funkcja
biblioteczna 
! (kopiuj acuch znakw) kopiuje acuch znakw z tablicy znakowej
bdcej argumentem konstruktora i po zakoczeniu kopiowania na kocu acucha dodaje
znak terminatora o kodzie zerowym.
Na rysunku 11.5 pokazano schematycznie kolejne stadia inicjowania obiektu w rezultacie
wykonania nastpujcej instrukcji.
;  +G    +   %K !  %L &'  

Rysunek 11.5.
Schemat obsugi pamici
dla drugiego konstruktora
konwersji z listingu 11.2

S trzy metody postpowania z polem danych zawierajcym wielko obszaru pamici przyporzdkowanego na stercie. Pierwszy sposb polega na tym, e takie pole zawiera cakowit
wielko pamici przydzielon dla okrelonego pola danych (liczba znakw do przechowywania plus jeden). Drugi sposb polega na tym, e takie pole zawiera liczb rzeczywicie
uytecznych znakw po dodaniu do tej liczby jedynki. Takie dodanie jedynki nastpuje
wtedy, gdy dane s umieszczane w pamici przyporzdkowanej na stercie. My uyjemy tu
tej drugiej metody, cho zdecydowanie trudno byoby wyjani, dlaczego ten drugi sposb
miaby by lepszy od pierwszego. Autor nie zmieni jednak zdania, poniewa wtedy musiaby wyjani, dlaczego ten pierwszy sposb jest lepszy od tego drugiego.

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

571

Trzeci sposb polega na tym, by nie przechowywa dugoci acucha znakw jako zawartoci numerycznego pola danych ani nie przechowywa jej wcale, lecz za kadym razem oblicza t dugo w czasie wykonania programu poprzez wywoanie funkcji bibliotecznej

 . To przykad wzgldnoci, a waciwie wspzalenoci czasu i przestrzeni. Ten trzeci
sposb jest lepszy, gdy dugo acucha znakw nie jest nam przydatna czsto, a z drugiej
strony odczuwamy niech do zajmowania pamici przez dodatkow liczb cakowit w kadym obiekcie obsugujcym acuchy znakw.
Skoro dynamiczny przydzia pamici na stercie nastpuje dla kadego obiektu w sposb indywidualny, wielu programistw moe mie odczucie, e taka dynamicznie przyporzdkowana pami powinna by rozpatrywana jako cz obiektu. Przy takim podejciu do problemu, obiekty klasy
  mog by postrzegane od strony kodu klienta jako obiekty o zmiennej
dugoci, zalenej od przyporzdkowanej dla danego obiektu wielkoci pamici na stercie.
Taki punkt widzenia jest uprawniony, ale tego typu spojrzenie prowadzi do bardziej skomplikowanych i zaskakujcych rozwaa o dziaaniu konstruktorw i destruktorw, a i zakca
nieco koncepcj samej klasy.
Autor preferuje tu podejcie zaprezentowane na diagramach pokazanych na rysunkach 11.4
oraz 11.5. Odzwierciedla to zasad C++, e klasa jest planem-szablonem dla poszczeglnych
obiektw danej klasy. Taki plan-szablon jest tu taki sam dla wszystkich obiektw klasy
 .
Zgodnie z tym planem, kady obiekt klasy
  zawiera dwa pola danych, a wielko kadego obiektu klasy
  jest taka sama. Gdy po stronie kodu klienta nastpuje wykonanie
nastpujcej instrukcji:
;  :$       5 0  

Dla obiektu
zostaje na stosie przydzielona pami przeznaczona na jego dwa pola danych.
Przydzia pamici na stercie nastpuje za porednictwem nalecych do tego obiektu klasy

  metod, ktre zostaj wywoane i wykonuj si w odniesieniu do danego, konkretnego
obiektu. Rne obiekty klasy
  mog mie przyporzdkowan rn wielko dynamicznej pamici na stercie. Mog (niezalenie) zwalnia t pami albo powiksza jej
objto, nie zmieniajc swojej tosamoci.
Takie podejcie w niczym si nie zmienia, jeli same obiekty, zamiast na stosie, lokalizowane
bd dynamicznie na stercie. Rozwamy nastpujcy przykad kodu klienta.
;  7
 F !&  &  ;       ( &  &<&
 #  ;   +I6+   :     8 &  

W tym przypadku obiekt klasy


  bez nazwy (wskazywany poprzez wskanik ) jest
tworzony na stercie, zajmujc tam miejsce przeznaczone na jedno wasne pole danych
liczb cakowit i na drugie wasne pole danych wskanik do acucha znakw. Po utworzeniu tego obiektu nastpuje wywoanie jego konstruktora i tene konstruktor dokonuje przyporzdkowania pamici na stercie dla wskazywanego acucha znakw ( ), czyli dodatkowo
dla czterech znakw oczywicie take na stercie. Prcz tego konstruktor inicjuje wskanik

 tak, by wskazywa na stercie pocztek tego czteroznakowego acucha znakw.
Takie podejcie pozwala na komfort mylenia o tych wszystkich obiektach jako o obiektach tej
samej klasy i tej samej wielkoci. Gdy tworzony jest nowy taki obiekt, przebiegaj tu dwa
odrbne procesy proces tworzenia obiektu (zawsze tej samej wielkoci) i wywoanie

572

Cz II n Programowanie obiektowe w C++


konstruktora dla tego obiektu (w celu jego zainicjowania). Konstruktor inicjuje pola danych
obiektu, wcznie ze wskanikiem, ktry zaczyna wskazywa obszar pamici na stercie.
Destruktor zwalnia dynamicznie przyporzdkowan na stercie pami. Destruktor jest wywoywany bezporednio przed usuniciem obiektu. Gdy obiekt jest usuwany, pami przyporzdkowana dla jego pl  oraz 
 zostaje zwolniona i udostpniona do dalszego swobodnego
wykorzystania. Jeli pami zostaje obiektowi przydzielona na stosie (tak jest w przypadku
obiektw ,  w funkcji   na listingu 11.2), po usuniciu obiektu zostaje zwolniona
i zwrcona na stos. Jeli sam obiekt zosta umieszczony na stercie (tak, jak obiekt bez nazwy wskazywany przez wskanik ), pami przydzielona dla jego pl 
 oraz  zostaje
zwrcona na stert. Jednake we wszystkich przypadkach, w ktrych destruktor zwalnia
pami (destruktor zwalnia pami wskazywan na stercie przez wskanik 
), ta pami
zostaje zwrcona na stert, zanim jeszcze znikn pola danych  oraz 
. W przeciwnym
razie instrukcja 

 w obrbie destruktora staaby si nielegalna.
Funkcja  ! powoduje zmian zawartoci dynamicznie przyporzdkowanej pamici
na stercie. Posugujc si funkcj biblioteczn 
 !, by upewni si, e zawarto pamici nie zostanie uszkodzona, funkcja ta dokonuje poprawnie kopiowania okrelonej iloci znakw, nawet wtedy, gdy kod klienta omykowo dostarczy acuch znakw, ktry jest
wikszy ni ilo pamici przydzielona na stercie dla obiektu klasy
 . W razie przepenienia funkcja 
 ! nie dodaje na kocu acucha znakw znaku terminatora. Z tego
powodu autor dodaje taki znak pod koniec kodu funkcji. Wydaje si to nadmiarowe w sytuacji, gdy nowy acuch znakw jest krtszy ni ilo miejsca dostpnego w pamici. Pamitaj, e w takim przypadku funkcja 
 ! zawsze wypeni zerami pozostae miejsce
w pamici, a dodanie jednego zera wicej nie spowoduje spowolnienia dziaania programu.
Funkcja  ! nie moe rozszerzy acucha znakw ponad jego pierwotn dugo. Wikszo projektw klas w rodzaju
  nie pozwala programicie na zmodyfikowanie zawartoci obiektu klasy
 . Skoro potrzebna jest inna zawarto obiektu, nie pozostaje nic
innego, jak tylko utworzy i uy innego obiektu z potrzebn nam zawartoci. Autor wybra
tu implementacj kompromisow. Pene oprzyrzdowanie pozwalajce na modyfikacje wymagaoby znacznie duszego kodu i zarazem przedyskutowania wielu dodatkowych zagadnie. Taka skromna funkcja  ! jest zupenie wystarczajca do niniejszej dyskusji.
Funkcja  zwraca wskanik do dynamicznie przydzielonej pamici na stercie. Listing 11.2
demonstruje dwa sposoby zastosowania tej funkcji po stronie kodu klienta w obrbie funkcji
 . Pierwsze zastosowanie tej funkcji nastpuje w celu wydrukowania zawartoci obiektu
klasy
 , ktry jest obiektem docelowym komunikatu (tego wywoania metody ).
Drugie wywoanie nastpuje w celu zmodyfikowania zawartoci obiektu poprzez wykorzystanie wartoci zwracanej przez t metod jako parametru wyjciowego w wywoaniu funkcji 
! w kodzie klienta. Pierwsze zastosowanie jest uprawnione, drugie jest aroganckie i napisane raczej w celu wprawienia serwisanta kodu w zakopotanie ni udzielenia mu
pomocy w zrozumieniu intencji projektanta kodu.
Jeden z pierwszych komputerowych jzykw programowania wysokiego poziomu, APL (skrt
od A Programming Language), by bardzo skomplikowany. Jest nadal stosowany, gwnie
w aplikacjach finansowych. Zestaw znakw tego jzyka jest tak ogromny, e wymaga on stosowania specjalnych klawiatur. Jzyk ten zawiera midzy innymi potne operacje do przetwarzania macierzy i tablic (ang. arrays and matrix processing). Programici lubi jzyk APL.
Za wyraz dobrego smaku uwaa si dowcip polegajcy na napisaniu kilku wierszy kodu w jzyku APL i pokazaniu go przyjacioom z pytaniem: Zgadnij, co to znaczy?.

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

573

Autor jest daleki od sugerowania, jakoby programistw o takim sposobie mylenia naleao
zwalnia z pracy. Nie mog oni jednak uczestniczy w grupowych projektach, w ktrych
inne osoby musz prowadzi obsug techniczn ich kodw. W dzisiejszych czasach zupenie
nie ma si czym chwali, jeli programista pisze takie kody, ktrych zrozumienie wymaga
dodatkowego wysiku.
  (     
    +I( 6+ 

Zauwa, e oburzenie autora skierowane jest przede wszystkim przeciw takim faktom, gdy
serwisant kodu musi woy dodatkowy wysiek w zrozumienie kodu. To, e podany kod
nie dokonuje uprzedniej oceny rozmiaru pamici dostpnej na stercie w obrbie danego obiektu
i z tego powodu moe doprowadzi do uszkodzenia danych w pamici jest oczywicie wane.
Ale to tylko drobiazg, ktry jednak powoduje wyczerpanie si naszej cierpliwoci. Mona
to skorygowa poprzez inny podzia odpowiedzialnoci pomidzy kodem klienta a obiektem
serwerem klasy
 .
   #       (  
   +I +     &E   )E  '0
    "    +I( + M &  

W przypadku obiektw klasy


  utworzonych za pomoc drugiego konstruktora konwersji warto parametru  
 to cakowita dostpna przestrze w pamici. Dla obiektw
utworzonych za pomoc pierwszego konstruktora konwersji warto parametru  
 okrela ostatni acuch znakw, zapamitany przy zastosowaniu danego obiektu. Ta ostatnia dugo moe by mniejsza ni caa dostpna przestrze w pamici. Co najwaniejsze, ta metoda
narusza zasad przenoszenia odpowiedzialnoci w d z kodu klienta na kody serwerw i zasad ukrywania szczegw technicznych dotyczcych manipulowania danymi przed kodem klienta.
W tym przypadku to kod klienta wykonuje operacje manipulowania danymi na niskim poziomie, nawet jeli nazwy pl obiektw klasy
  nie s stosowane w kodzie klienta. Jeli
chcemy ochroni dane znajdujce si na stercie przed ryzykiem ich uszkodzenia, to kod serwera powinien zawiera odpowiednie instrukcje, ktre sprawdzaj rozmiar dostpnej dynamicznej pamici. Dobrym rozwizaniem powinno tu by uycie po stronie klienta nazwy
funkcji usugowej (metody serwera) zamiast manipulowania danymi serwera bezporednio
i przerzucenie odpowiedzialnoci za ochron spjnoci pamici na stercie na serwer. Czy to
jest oczywiste? Oto rozwizanie, ktre wykonuje t prac dobrze, jest bezpieczne i nie wymaga adnych dodatkowych wyjanie. To rozwizanie ju widziae.
   B 54 &' +I +"
 1+I( 6+      (    0( 0

Na rysunku 11.6 pokazano wydruk wyjciowy programu z listingu 11.2. Ten wydruk demonstruje, e wywoanie funkcji  ! chroni dynamiczn pami przed przepenieniem
(ang. overflow) poprzez obcicie danych z kodu klienta (ang. truncate).
Rysunek 11.6.
Wydruk wyjciowy
programu z listingu 11.2

574

Cz II n Programowanie obiektowe w C++


Zastosowanie wskanika zwrconego przez funkcj  nie zawiera takiej ochrony pamici. Oto przykad uszkodzenia danych w pamici, do ktrego moe doprowadzi metoda

 **.
 7  #      && ) )E  !&  
 =:$$> # HNH   &   ) 0

Albo jeli wolimy notacj acuchow przy stosowaniu obiektw, moemy zrobi to samo,
uywajc tylko jednej instrukcji.
   =:$$> # HNH  && ) )E  &  0

To nie jest dobry ani zalecany sposb programowania.

Ochrona danych na stercie nalecych do obiektu od strony kodu klienta


C++ zapewnia nam sposb ochrony wntrza obiektu od strony kodu klienta, ktry posuguje si wskanikiem zwrconym przez metod. Zadeklarowanie takiego wskanika jako
wskanika do staej zapobiega takim kopotom. Na przykad zdefiniujmy warto zwracan
przez metod  jako wskanik do staej znakowej, a nie jako wskanik do zmiennej znakowej, jak to byo na listingu 11.2.
   &<&  5(      "
  7 ; ""       & &

Teraz, gdyby kod klienta podj prb zmodyfikowania zawartoci dynamicznie przyporzdkowanej pamici za porednictwem wskanika bdcego wartoci zwracan poprzez
metod , kompilator oznaczy tak prb jako bd skadniowy.
    +I +   +I +  +I( +
  5   !5. &5

Przy takim projekcie klasy usugowej (serwera)


  kod klienta jest zmuszony do posugiwania si funkcj  ! w celu zmiany stanu obiektu. W efekcie kod klienta zostaje
wyraony w formie wywoa funkcji serwera, odpowiedzialno za bezpieczestwo operacji zostaje przekazana w d na klas-serwer, kod klienta nie jest zmuszany, by przejmowa
si technicznymi szczegami wewntrznej konstrukcji projektu klasy (tj. nie musi wiedzie
o ograniczeniach miejsca na stercie).

Przeciony operator konkatenacji acuchw znakowych


Naszym kolejnym krokiem bdzie zaprojektowanie funkcji operatorowej dokonujcej przecienia operatora dodawania wobec klasy
  w taki sposb, by operator ten pozwala
na konkatenacj dwch obiektw klasy
 . Konkatenacja obiektw bdzie polega na
doczeniu zawartoci drugiego obiektu (tekstu wskazywanego przez wskanik) do koca
zawartoci pierwszego obiektu (tekstu wskazywanego przez wskanik). Oznacza to, e po
stronie kodu klienta taki przeciony operator bdzie mg zosta uyty w nastpujcy sposb.
;  +G     +    
;  +?     +    
 2#   F"    

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

575

Po wykonaniu tego fragmentu kodu klienta zawarto reprezentowana przez obiekt  powinna
pozosta bez zmian, natomiast zawarto reprezentowana przez obiekt powinna stanowi
poczenie dwch acuchw znakw: +   

,
   
Jeli zaimplementujemy ten operator w formie metody operatorowej, to obiekt powinien
by docelowym obiektem komunikatu, natomiast obiekt  powinien by biecym argumentem w danym wywoaniu takiej metody operatorowej. Rzeczywiste znaczenie ostatniego
wiersza w podanym fragmencie kodu jest nastpujce:
   2#  *  !&      
  2# 

Skoro tak, interfejs tej funkcji powinien zawiera modyfikator  


w odniesieniu do parametru funkcji, natomiast nie powinien zawiera modyfikatora  
w odniesieniu do samej
metody (tj. do modyfikowanego docelowego obiektu komunikatu). Typem wartoci zwracanej
przez t metod moe by  . Ograniczy to stosowanie operatora, nie zezwalajc na skadni wyrae acuchowych, ale z punktu widzenia autora kodu klienta nie jest to powane ograniczenie.
 & & ( B  5.    !&    
    2#   ;  

Pamitamy, e nie zaleca si przekazywania obiektw poprzez warto, ale przyjmujemy


zaoenie, e w tym przypadku nie mamy problemw z efektywnoci dziaania programu.
W kocu obiekt klasy
  zawiera tylko dwa niewielkie pola danych wskanik do
acucha znakw i liczb cakowit. Kopiowanie takich pl danych nie powinno potrwa
zbyt dugo.
Algorytm konkatenacji acuchw znakowych powinien zawiera nastpujce czynnoci:
1. Dodanie dugoci dwch tablic znakowych, by okreli sumaryczn, wynikow

liczb znakw.
2. Przydzieli dynamicznie na stercie ilo pamici wystarczajc do przechowywania

tej wynikowej liczby znakw plus znak koca zero.


3. Sprawdzenie powodzenia operacji przydziau pamici i przerwanie operacji, jeli

w systemie zabrako pamici.


4. Kopiowanie acucha znakw z docelowego obiektu komunikatu do nowej, przydzielonej

na stercie pamici.
5. Kopiowanie tablicy znakowej z obiektu przekazanego jako argument do nowej,

przydzielonej pamici (z doczeniem do koca poprzedniego acucha).


6. Ustawienie wskanika 
 w obiekcie docelowym komunikatu tak, by wskazywa

now przydzielon na stercie pami.


Na rysunku 11.7 pokazano schematycznie te czynnoci (poza przerwaniem, gdyby w systemie
zabrako pamici na stercie) i instrukcje C++ implementujce te dziaania. Po stronie kodu
klienta autor uy nieco krtszych acuchw znakw, by uatwi ledzenie zdarze.

576

Cz II n Programowanie obiektowe w C++

Rysunek 11.7.
Schemat pamici
dla funkcji
operatorowej
konkatenacji
acuchw
znakowych

W najwyszej czci rysunku pokazano dwa obiekty klasy


 , obiekt (reprezentujcy
tekst ( ) oraz obiekt  (reprezentujcy tekst
). Stadium (A) pokazuje obydwa te obiekty
po tym, jak pole  pierwszego obiektu zostao zmodyfikowane, przydzielona zostaa pami
na stercie i istniejca (pocztkowa) zawarto obiektu zostaa skopiowana na stert do tej
nowej, przydzielonej pamici (po wykonaniu krokw 1 4 algorytmu). Stadium (B) pokazuje
stan pamici na stercie po wykonaniu kroku 5. Stadium (C) pokazuje stan obu tych obiektw
po tym, jak wskanik 
 zawarty w docelowym obiekcie zacz wskazywa now przyporzdkowan pami (krok 6).
Po zebraniu tego wszystkiego razem otrzymujemy nastpujcy kod po stronie serwera:
  ; ""   2#   ;   !& (&  
 7    &  &<&
 #   2      5&   5 )E
 #  = 2 %>  5 0  
1 ##?@AA 3 %     0   5
    &    ( 0) &  & 
      & & ( ( 0) & 
 #  ,     !  &5 0  . 0E

Moe wyda si nieco przesadne, by w celu wyjanienia kolejnych krokw tak prostego algorytmu zagbia si w tak drobne szczegy i wykrela odrbny rysunek dla kadego
malekiego kroku w zarzdzaniu pamici. Jeli tak to odczuwasz to bardzo dobrze. Ale
naleysz do szczliwej mniejszoci. Dla wikszoci ludzi operacje przy uyciu wskanikw s tajemnicze i sprzeczne z intuicyjnym wyczuciem.

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

577

Tylko dowiadczeni programici s tu w stanie zauway, e przestrze na stercie posiadana przez docelowy obiekt komunikatu nie zostaje poprawnie zwrcona do swobodnego
wykorzystania. Ten rysunek pokazuje to w sposb klarowny.
Autor uwaa, e rysowanie takich schematw to jedyny sposb na wyrobienie sobie intuicji
w odniesieniu do zarzdzania pamici i wychwytywania bdw. Lepiej spdzi kilka dodatkowych minut na rysowaniu i planowaniu, ni straci pniej godziny z debugerem i z innymi
skomplikowanymi narzdziami, poszukujc drogi w gszczu, bagnach i zarolach, pord
instrukcji, ktrych znaczenie i dziaanie nie jest dla nas do koca zrozumiae.
Takie rysunki pozostaj, oczywicie, tylko narzdziami. To my musimy tak uywa tych narzdzi, by upewni si, e dokadnie rozumiemy kad instrukcj.

Zapobieganie wyciekom pamici


Jak ju wspomniaem, na rysunku 11.7 pokazano, e pami zajta przez tablic znakow
na stercie, a wskazywana przez wskanik 
 docelowego obiektu na pocztku wykonania
funkcji, nie jest poprawnie zwracana do ponownego uytku. Gdy wskanik 
 zostaje skierowany tak, by wskazywa nowy, przyporzdkowany segment pamici (wskazywany przez
lokalny wskanik ), ta stara pami staje si niedostpna (ang. unaccessible). To wyciek pamici powszechny bd przy manipulowaniu wskanikiem w zarzdzaniu pamici. Aby
zapobiec takiemu wyciekowi pamici, pami zajmowana przez star tablic znakow powinna zosta poprawnie zwrcona na stert, zanim jeszcze wskanik 
 zostanie przestawiony i zacznie wskazywa now przydzielon pami i now (wynikow) macierz znakow.
  ; ""   2#   ;   !& (&  
 7    &  &<&
 #   2      5&   5 )E
 #  = 2 %>  5 0  
1 ##?@AA 3 %   <  0   5
    &    ( 0)
      & & ( ( 0)
      ( ( 0
 #  ,     !  &5  . 0E

Rysunek 11.8 jest podobny do rysunku 11.7. Pokazuje schematycznie, jak tablica znakowa
umieszczona na stercie, a wskazywana poprzez wskanik 
 docelowego obiektu komunikatu znika w wyniku zadziaania operatora 
. Dopiero potem wskanik 
 zostaje przestawiony tak, by wskazywa now macierz znakow na stercie.
Po usuniciu tego wycieku pamici naleaoby si przyzna, e w tej dyskusji o funkcji dokonujcej przecienia operatora autor mwi sam prawd i tylko prawd, ale nie powiedzia
jeszcze caej prawdy. Powodem byo to, e najpierw naleao si upewni, e pokonalimy
ju mniejsze i atwiejsze przeciwnoci, zanim staniemy oko w oko z bardziej skomplikowanymi i bardziej niebezpiecznymi problemami. Autor chcia utrzyma uwag czytelnika w stanie
niepodzielnym.
Ta dyskusja ma za zadanie zaprezentowanie swoistych szablonw postpowania i niebezpieczestw, ktre powinnimy rozpoznawa, gdy piszemy wasne programy w C++. Istota
tego problemu to, rzec mona, ulubiony przeciwnik autora przekazywanie obiektw jako
parametrw poprzez warto.

578

Cz II n Programowanie obiektowe w C++

Rysunek 11.8.
Schemat pamici
dla skorygowanej
funkcji operatorowej
konkatenacji
acuchw
znakowych wobec
klasy String

Ochrona integralnoci programu


Gdy biecy argument, obojtne obiekt, czy te nie jest przekazywany poprzez warto, jego warto zostaje skopiowana do utworzonej na stosie lokalnej automatycznej
zmiennej. Taka kopia jest wykonywana metod komponent po komponencie. To nie stanowi
adnego problemu dla danych wbudowanych typw elementarnych, ale jest pewn uciliwoci i powoduje nieznaczne pogorszenie efektywnoci w przypadku takich klas, jak 
 
lub . Stanowi to realny problem, z punktu widzenia efektywnoci dziaania kodu,
dla wikszych klas, ktrych obiekty wymagaj wikszych iloci pamici.
Co najwaniejsze, stanowi to ogromny problem z punktu widzenia integralnoci programu,
jeli taka klasa zawiera wskaniki wskazujce dynamicznie przydzielon pami na stercie.
Popatrzmy na wykonanie takiej funkcji z parametrem przekazywanym poprzez warto w krytycznych momentach wykonania funkcji na pocztku, w chwili wywoania funkcji i na
kocu, gdy wykonanie funkcji zostaje zakoczone. Autor lubi kojarzy te momenty z klamrowym nawiasem otwierajcym i z klamrowym nawiasem zamykajcym, ograniczajcymi kod
ciaa funkcji.

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

579

Gdy podczas przekazywania parametru poprzez warto wykonywana jest kopia biecego
argumentu-obiektu, nastpuje wywoanie dodanego przez kompilator konstruktora kopiujcego. Ten konstruktor kopiuje pola danych biecego argumentu do odpowiednich pl danych jego lokalnej kopii obiektu bdcego parametrem formalnym funkcji. Gdy kopiowany
jest wskanik 
, wskanik w obiekcie stanowicym parametr formalny (lokalnej kopii) otrzymuje skopiowan zawarto wskanika 
 (adres) z biecego argumentu. Ten skopiowany wskanik wskazuje adres na stercie, gdzie przechowywana jest tablica znakowa reprezentowana przez obiekt bdcy biecym argumentem funkcji.
W efekcie wskaniki w obu tych obiektach biecym argumencie i jego lokalnej kopii
wskazuj t sam sekcj pamici na stercie, a kady z tych obiektw uwaa, e ma t
pami do wycznego uytku.
T sytuacj prbowano przedstawi schematycznie na rysunku 11.9. W istocie to, co dotd
zostao powiedziane, nie zmienia dziaania funkcji operatorowej dokonujcej przecienia
operatora (na razie). To dlatego wszystko to, co powiedziano do tej pory, byo sam prawd
i tylko prawd.
Rysunek 11.9, ktry mwi ca prawd, zawiera (dodatkowo obejmuje) lokalny obiekt ,
ktrego pola danych s inicjowane poprzez skopiowanie pl danych biecego argumentu
funkcji obiektu . Rysunek 11.9a pokazuje, e ten lokalny obiekt  oraz biecy argument  odwouj si do tej samej sekcji pamici na stercie. Rysunek 11.9b pokazuje, e
po przyporzdkowaniu i zainicjowaniu nowej pamici na stercie ta nowa pami zastpia star
w docelowym obiekcie komunikatu ( ), a lokalny obiekt  oraz biecy argument  nadal odwouj si do tej samej sekcji pamici na stercie.
Rysunek 11.9.
Schemat pamici
przy przekazaniu
przez warto obiektu
klasy String

580

Cz II n Programowanie obiektowe w C++


Caa prawda powinna jeszcze obejmowa zakoczenie wykonania tej funkcji. Gdy wykonanie
kodu funkcji dochodzi do zamykajcego nawiasu klamrowego (to take koniec zakresu widocznoci nazw w obrbie funkcji), lokalna kopia obiektu (
 ) jest usuwana. Z punktu
widzenia konwencjonalnej intuicji programistycznej oznacza to, e pami zajmowana przez
taki lokalny obiekt (wskanik i liczba cakowita w tym przypadku) powinna znikn (zosta
automatycznie zwolniona i zwrcona na stos). Ale w C++ nie ma czego takiego, jak skasowanie obiektu i ju. Kade usunicie obiektu musi zosta poprzedzone wywoaniem destruktora.
Destruktor, gdy zostaje wywoany, czyni to, co wynika z kodu destruktora. Zwalnia i zwraca na stert (jako wolny i dostpny do dalszego uytkowania) ten segment pamici, ktry
wskazywa wskanik 
 znajdujcy si w usuwanym obiekcie.
; ""/;     =>  ,
  0  &(   &<& 

Rysunek 11.9c przedstawia stan lokalnego obiektu  i biecego argumentu  po wywoaniu destruktora, ale zanim lokalny obiekt zostanie usunity. Rysunek pokazuje, e i lokalny
obiekt, i biecy argument straciy przyporzdkowan im pami na stercie (pami wskazywana przez wskanik 
 zostaa zwolniona). To dziaanie oczywicie nie ma wpywu na
stan obiektu docelowego , poniewa obiekt docelowy nie jest usuwany. Gdy zakoczy si
wykonanie funkcji przeciajcej operator, obiekt docelowy pozostanie w dokadnie tym
samym stanie, co podczas poprzedniej dyskusji, odzwierciedlonej schematycznie na rysunku 11.8. Taki kod klienta zwraca poprawne rezultaty.
;  +I +  ;  + 6+ 
 2# 
 

+  # +

  

  & & " +I 6+

Tym niemniej pami zwolniona i zwrcona do systemu przez destruktor, gdy usuwany by
formalny parametr obiekt , nie naleaa do obiektu docelowego. Naleaa (i nadal powinna
nalee) do biecego argumentu funkcji, czyli do obiektu  zdefiniowanego w przestrzeni
klienta. Po wywoaniu tej funkcji obiekt klienta, ktry by uyty jako biecy argument przekazany przez warto zosta pozbawiony przydzielonej mu uprzednio dynamicznie jego
pamici na stercie. Jeli kod klienta po wywoaniu tej funkcji sprbuje ponownie uy tego
obiektu spowoduje to wystpienie bdu.
;  +I +  ;  + 6+ 
 

+  # +

  

  & +I +


 

+  # +

  

  & + 6+


 2# 
 

+  # +

  

  & +I 6+


 

+  # +

  

  &  +)+

Nie wyglda szczeglnie elegancko ani mdrze powtrne kontrolowanie zawartoci obiektu ,
ktra bya przed chwil drukowana; sam obiekt cakiem niedawno by uywany jako rwarto
w wywoaniu funkcji operatorowej 
. Nastpuje to tylko dlatego, e dokadnie
wiemy, e tu wanie wystpuje problem z tak implementacj. Jest jasne, e obiekt powinien
reprezentowa dokadnie t sam zawarto, ktr przed chwil reprezentowa, uczestniczc
w wyraeniu   . Tak podpowiada intuicja programisty przyzwyczajonego do konwencjonalnego programowania. W wikszoci przypadkw w C++ ta intuicja znajdzie potwierdzenie, ale nie zawsze, i moliwie jak najszybciej powinnimy rozwin u siebie inn intuicj.
To wszystko zostao tu powiedziane, poniewa ten niewinnie wygldajcy kod klienta moe

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

581

doprowadzi do sytuacji, gdy tekst reprezentowany przez obiekt  bdzie absolutnie przypadkowy, a wszelka prba uycia tego obiektu przy zaoeniu, e jego stan pozostaje niezmienny, jest zwyk lekkomylnoci.
No i jak ci si to podoba? Programowanie w C++ nie pozwala si nudzi. Niemniej jednak
programista piszcy w C++ musi rozumie, co dzieje si w sposb niejawny, pod stoem,
nawet w takim prostym programie, jak ten ostatni przykadowy fragment kodu.
To jeszcze nie koniec tej opowieci. Takie efekty wystpuj przy jeszcze jednym zamykajcym nawiasie klamrowym zamykajcym zakres widocznoci nazw. Zawsze zwracaj uwag
na nawiasy klamrowe ograniczajce zakresy widocznoci (dostpnoci). Powoduj one wykonanie znaczcej czci pracy. Gdy kod klienta dochodzi do nawiasu klamrowego zamykajcego jego przestrze widocznoci nazw i zamierza zakoczy swoje dziaanie, dla wszystkich lokalnych obiektw s wywoywane destruktory, wcznie z nieszczsnym obiektem ,
ktry by uywany przy wywoywaniu funkcji operatorowej i tu przed zakoczeniem tej
funkcji (powrotem z funkcji) zosta pozbawiony swojej dynamicznie przydzielonej pamici.
Destruktor prbuje zwolni pami wskazywan przez zawarty w tym obiekcie wskanik 
,
jednake ta pami na stercie zostaa ju uprzednio uznana za niewan i zwrcona do systemu. Gdybymy projektowali jzyk programowania, moglibymy wprowadzi jakie no op
(NOP ang. no operation instrukcja nie powodujca adnego dziaania). Ale tu nam si
nie poszczcio. Nie z C++ te numery, Brunner. W C++ powtrne uycie operatora 

w stosunku do tego samego wskanika jest zabronione. To jest bd.
Niestety, stwierdzenie to jest bd nie oznacza, e kompilator powstrzyma si od kompilacji i wydrukuje komunikat o bdzie skadniowym tak, bymy mogli to skorygowa. Projektant kompilatora nie ponosi odpowiedzialnoci za ledzenie wykonania kodu i informowanie nas, e popenilimy bd. Taki kod jest skadniowo poprawny. Nie oznacza to take,
e taki program skompiluje si, uruchomi, wykona i bdzie wykazywa w sposb powtarzalny nieprawidowe rezultaty. To znaczy tylko tyle, e rezultaty dziaania takiego kodu oka
si nieokrelone. W istocie rezultaty te s zalene od platformy uruchomieniowej, od tego,
w jaki sposb aplikacja okae si zalena od rodowiska operacyjnego. System moe si
zawiesi, program moe zadziaa w sposb nieprawidowy (znienacka), moe te przez pewien czas dziaa cakowicie poprawnie (do czasu).
Na listingu 11.3 pokazano kompletny program zawierajcy implementacj tej wadliwej
konstrukcji. Wyjciowy wydruk tego programu na monitorze autora przedstawiono na rysunku 11.10.
Listing 11.3. Przecienie operatora konkatenacji z obiektem parametrem przekazywanym
poprzez warto

 
    
 ; 

 7    0  !  
 
!"
;    #$   &  &  &  (   )
;   7   &  &  &  (
/;     5 ( 0
    2#   ;    & & (  !&  ; 

582

Cz II n Programowanie obiektowe w C++

Rysunek 11.10.
Wydruk wyjciowy
programu
z listingu 11.3

   1  7     ) ! & (


  7         &<&  !
, 
; "";   
  #  
 #  =2%>
1  ##?@AA 3 % 
=$> # $ ,    54 5 )  ( B ( CD
; "";   7
  #      5 ) &  () 
 #  =2%>  5  (.(  ) (   
1  ##?@AA 3 %   <  0   5
   ,  & ( ()  &   0
; ""/; 
    ,   0     &<&6
  ; ""   2#   ;     )E
  #   2      5&   5 )E
 7 #  = 2 %>    (..  )E ( 
1 ##?@AA 3 %   <  0   5
    & (  . 0)E &  & 
       ( . 0)E & 
    F  )E
 #  ,       F &.E
  7 ; ""       4   .
    ,
  ; "" 1   =>   ! . 0.
  *%      5
=*%> # $ ,    & 4 54 &'
 

;  +G     + 
    " ;  +G (   + 
;  +?     + 
    " ;  +?   F '()E <+ 
 

+  # +

  

  & ( CD

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

583

 

+  # +

  

  & ( CD
 2#     2# 
 

+  # +

  

  & ( CD
 

+  # +

  

   &  !0 CD


 1+A    1   ! +    &  0
    " ;  +?  (  H!H+ 
*  & 
 

+  # +

  

  OOOO
  $
,

Zauwa, e te wszystkie okropne rzeczy dziej si w chwili zakoczenia dziaania funkcji.


Pierwsze nieprzyjemne zdarzenie miao miejsce wtedy, gdy przeciajca funkcja operatorowa

 zbliaa si ku kocowi i musiao (zgodnie z zasadami) nastpi wywoanie destruktora wobec formalnego parametru tej funkcji. Wtedy to biecy argument tej funkcji,
obiekt , zosta pozbawiony przydzielonej mu na stercie dynamicznej pamici. Drugie niemie zdarzenie miao miejsce, gdy funkcja-klient,   zbliaa si ku kocowi i obiekt 
musia zosta usunity, poniewa koczy si zakres przestrzeni widocznoci jego nazwy
(obiekt wychodzi poza zakres ang. out of scope). Jego pami na stercie zostaa wtedy
zwolniona i zwrcona po raz wtry.
W istocie, w C++ bdem jest powtrne zwracanie dynamicznej pamici poprzez ponowne
uycie operatora 
 wobec tego samego niezerowego wskanika. Jeli wskanik zawiera ,-.., to nie jest bd, lecz operacja pusta (ang. no operation). Niektrzy programici
prbuj rozwiza ten problem poprzez przypisanie wskanikowi do pamici na stercie wartoci ,-.. w obrbie destruktora4.
; ""/; 

     0  
 # $      ! &.E   '  
,

To sympatycznie wygldajcy pomys, ale nie dziaa zgodnie z ich intencjami. Taki wskanik, ktry zosta ustawiony na zero, naley do obiektu, ktry to obiekt bdzie usuwany w cigu
najbliszych mikrosekund. Nadal istnieje drugi wskanik, ktry wskazuje ten sam adres pamici
i mgby zosta wyzerowany, ale nie jest dostpny dla takiego destruktora, wykonujcego
si przecie w odniesieniu do innego obiektu. Poza tym, nawet gdyby to zadziaao, stanowioby to tylko rodek zapobiegajcy wykonaniu bdnej instrukcji, nie powodujc przecie
przywrcenia pamici, ktra zostaa omykowo skasowana.

Jak std przej tam?


Czy autor przestraszy czytelnika? Jeli tak, to taka wanie bya intencja. Jeli nie, nie szkodzi,
pamitaj jednak zawsze, by zatroszczy si o dynamiczne zarzdzanie pamici w swoich
programach. Nawet jeli na twoim komputerze programy dziaaj prawidowo, to jeszcze nie
jest oczywisty dowd, e dany program jest poprawny (dodajmy to do listy naszych zasad
testowania programw).
4

Taki wskanik przestaje wskazywa cokolwiek przyp. tum.

584

Cz II n Programowanie obiektowe w C++


Program moe dziaa bez adnych zgrzytw miesicami i latami, a nastpnie, np. po zainstalowaniu w systemie jakiej innej, zupenie z nim nie zwizanej aplikacji bd po aktualizacji
systemu operacyjnego do nowszej wersji Windows zmieni si sposb wykorzystywania
pamici i nasz program doprowadzi do katastrofy. Taki program moe take dawa nieprawidowe rezultaty, ktre nie zostan dostrzeone, poniewa dziaa przecie poprawnie przez
wiele miesicy, a nawet przez wiele lat. Co si wtedy dzieje? Czy przeklina Microsoft, bo
wanie zaktualizowalimy system operacyjny? Przecie to nie jest wina Microsoftu. To bd
programisty piszcego w C++, ktry popeni grzech zaniechania, nie postawiwszy jednego
znaku ampersand ($) w interfejsie funkcji dokonujcej przecienia operatora 
.
Oto jak powinna wyglda ta funkcja. Jej obiekt-parametr nie jest przekazywany poprzez
warto, lecz poprzez referencj.
  ; ""   2#   ;  -  1(   

 #   2      5&   5 )E
 7 #  = 2 %>    (..  )E ( 
1 ##?@AA 3 %   <  0   5
    & (  . 0)E
      & ( . 0)E
    ! F  )E
 #  ,     F &.E

Na rysunku 11.11 pokazano wydruk wyjciowy programu z listingu 11.3 z funkcj operatora konkatenacji przy przekazaniu jej parametru poprzez referencj.
Rysunek 11.11.
Wydruk wyjciowy programu
z listingu 11.3 z funkcj
przeciajc operator
konkatenacji, w ktrej
parametr zosta przekazany
poprzez referencj

Naley zdecydowanie uruchomi ten program, poeksperymentowa z nim, by dokadnie zrozumie te zagadnienia, ktre mog sprawia problemy. Nie ulegaj pokusie przekazywania
obiektw jako parametrw poprzez warto, chyba e jest to absolutnie niezbdne.
To prawdziwa okropno, e dodanie bd usunicie jednego tylko znaku w kodzie rdowym (ampersanda $) moe zmieni zachowanie si programu w tak dramatycznym stopniu.
Zwr uwag, e obie wersje kodu s ze skadniowego punktu widzenia poprawne. Kompilator nie uprzedzi nas, e jest pewien problem, ktrym naleaoby si martwi.
Przekazywanie obiektu jako parametru poprzez warto przypomina kierowanie czogiem.
Zawsze dojedziemy tam, dokd chcemy, ale po drodze moemy cakiem niezamierzenie
dokona wielu zniszcze. Jak ju powiedziano wczeniej, opieraj si pokusie przekazywania obiektw poprzez warto, chyba e jest to absolutnie konieczne.
Nie przekazuj obiektw do funkcji poprzez warto. Jeli obiekty zawieraj wewntrz
wskaniki i dynamicznie zarzdzaj pamici na stercie, nie mona nawet myle
o przekazaniu takich obiektw do funkcji poprzez warto. Przekazuj obiekty do funkcji
poprzez referencje i nie zapominaj o uyciu modyfikatora   , jeli funkcja nie modyfikuje stanu obiektu-parametru i (lub) stanu docelowego obiektu komunikatu.

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

585

Wicej o konstruowaniu kopii obiektw


Popatrzmy wstecz, z pewnej perspektywy na t sytuacj. Istota problemu omawianego w poprzednim podrozdziale sprowadza si do kopiowania obiektu, ktry jako pola danych zawiera
wskaniki wskazujce segmenty pamici na stercie.
Kady taki obiekt powinien wskazywa obszar pamici, ktry zosta przydzielony wycznie
dla niego. Na przykad klasa
  zawiera wskanik, ktry wskazuje segment pamici na
stercie zawierajcy tablic znakow zwizan z konkretnym obiektem klasy
 .
Gdy pola danych zawarte w jednym obiekcie s kopiowane na pola danych innego obiektu,
odpowiednie wskaniki w obu tych obiektach bd mie tak sam zawarto, bd zatem
wskazywa ten sam obszar pamici na stercie. Takie obiekty mog by usuwane (koczy
swoje ycie) w rnych momentach. Na przykad parametr formalny funkcji z listingu 11.3
znika, gdy ta funkcja zakoczy swoje dziaanie, a jej rzeczywisty argument pozostaje przy
yciu w przestrzeni klienta w obrbie funkcji  . Gdy obiekt ma przesta istnie, jego
destruktor zwalnia pami wskazywan przez wskanik (czasem wskaniki) zawarte w takim
obiekcie. W ten sposb drugi spord tych obiektw, nadal yjcy, po cichu traci zwizane
z nim dane zapamitane na stercie. Wszelka prba uycia potem takiego obiektu, ktry utraci
swoje dane na stercie, jest niepodana. To jest po prostu bd.
Jeli taka pami, zwrcona na stert, nie zostanie natychmiast powtrnie uyta do innych
celw, taki obiekt-fantom moe zachowywa si tak, jakby ta zwrcona pami nadal bya
zastrzeona dla niego. Nasze testy mog utwierdzi nas w przekonaniu, e program jest poprawny.
Gdy znika drugi spord tych obiektw, nastpuje wywoanie jego destruktora. Zwr uwag,
e nie uyto tu sformuowania destruktor jest wywoywany ponownie. Ten destruktor by
wywoywany wczeniej, ale wobec innego obiektu (formalnego parametru funkcji), tego, ktry
zosta ju usunity. Teraz ten sam destruktor jest wywoywany wobec drugiego spord tych
obiektw (rzeczywistego argumentu funkcji) i prbuje zwolni i zwrci do systemu ten sam
segment pamici na stercie. W C++ powoduje to powstanie sytuacji bdnej i zachowanie
programu jest tu nieprzewidywalne. To taka grzeczna forma wypowiedzenia myli, e w tym
momencie program moe zrobi wszystko, co mu si spodoba.

Sposb na zachowanie integralnoci programu


Jest wiele rodkw zapobiegawczych, ktrych mona uy, by zapobiec problemom pojawiajcym si wtedy, gdy obiekty z dynamicznie przyporzdkowan pamici s przekazywane jako parametry poprzez warto.
Jednym ze sposobw jest wyeliminowanie destruktora, ktry zwalniaby i zwraca zbdn
ju pami na stercie do systemu. To nie jest ani dobre rozwizanie na stae, ani dobre rozwizanie w ogle. Moemy zdecydowa, e zastosujemy je jako rozwizanie tymczasowe,
gdy program zaamie si i musimy go uruchomi w celu przeprowadzenia sesji diagnostycznej z debugerem. Takie wyczenie destruktora pozwoli na wykonanie programu od pocztku do koca.

586

Cz II n Programowanie obiektowe w C++


Innym rodkiem zapobiegawczym jest zastosowanie we wntrzu obiektw tablic znakowych o staych rozmiarach zamiast dynamicznego przyporzdkowania pamici. To nie jest
eleganckie rozwizanie, ale mona je zastosowa, jeli rozmiar takiej tablicy zostanie wybrany szczodrze (z zapasem). Jest to szczeglnie suszne w przypadku tych programw,
ktre posuguj si stosunkowo niewielk liczb obiektw i niezwykle rzadko musz dokonywa obcinania danych, ktre nie zmieszcz si w tablicy o staej wielkoci, a poza tym
jeli jest to akceptowalne z punktu widzenia integralnoci danej aplikacji.
Odnonie samego przekazywania parametrw, najlepszym rodkiem zapobiegawczym jest
tu przekazanie obiektw do funkcji jako parametrw poprzez referencje, a nie poprzez
warto. Eliminuje to problem tworzony przez kopiowanie obiektu. Przyspiesza to take
wykonanie programu poprzez wyeliminowanie potrzeby tworzenia i usuwania tymczasowych
obiektw, wywoywania konstruktorw i destruktorw.
Niestety, to rozwizanie nie jest uniwersalne. S przypadki kopiowania zawartoci jednego
obiektu do innego obiektu, ktre nie s zwizane z przekazywaniem parametrw do funkcji,
kiedy to takie rozwizanie nie moe zosta zastosowane. Bywaj przypadki, gdy jeden obiekt
jest inicjowany przy uyciu innego obiektu tej samej klasy. Rozwamy nastpujcy fragment
kodu, w ktrym nastpuje przekazanie parametrw do funkcji 
 poprzez referencj.
    )    0 B &  
;  +G     +  +?     + 
    " +G (   +  +?   F '()E <+ 
 

+  # +

  

  & ( CD
 

+  # +

  

  & ( CD
 2#     2#  ! 1
 

+  # +

  

  & ( CD
 

+  # +

  

  CD B   1(0


 1+A    1   ! +   !  &  0
 +?  (  H!H+
;  #   (  !& 
 

+ # +

  

  & ( CD
 1+?     +    1&(    
 

+ # +

  

  & ( CD
 

+  # +

  

   &F  1& 

Ten kod tworzy dwa obiekty klasy


  i , inicjuje je za pomoc konstruktora konwersji i dokonuje konkatenacji reprezentowanych przez te obiekty acuchw znakowych.
Skoro obiekt  jest przekazywany jako argument do funkcji poprzez referencj, nie ma tu
zakcenia dziaania pamici i obiekt  zachowuje przyporzdkowan mu pami na stercie.
Jeli zmodyfikujemy obiekt , zmieni si tylko jego zawarto, natomiast zawarto obiektu
pozostanie bez zmian. Nastpnie utworzymy jeszcze jeden obiekt
klasy
 , ktry
zainicjujemy poprzez skopiowanie dotychczasowej zawartoci obiektu . Gdy modyfikujemy
zawarto obiektu
, intuicyjnie zakadamy, e zawarto obiektu  pozostanie bez zmian.
Na rysunku 11.12 pokazano oczekiwane rezultaty wykonania podanego fragmentu kodu.
Rysunek 11.12.
Oczekiwany (a nie
rzeczywisty) wydruk
wyjciowy podanego
fragmentu kodu klienta

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

587

W prawdziwym, realnym yciu programisty nie wszystko jednak przebiega zgodnie z naszymi oczekiwaniami. Na listingu 11.4 pokazano kod klasy
  (z przekazywaniem parametru do funkcji 
 poprzez referencj) i kod klienta zawierajcy podany powyej
fragment kodu. Fragment ten na listingu zmodyfikowano w taki sposb, e obiekt
zosta
utworzony w zagniedonym zakresie widocznoci nazw (ang. nested scope). Gdy ten zagniedony segment kodu bdzie si zblia do zakoczenia i obiekt
zostanie usunity,
moemy zweryfikowa stan obiektu  i skontrolowa jego integralno. Na rysunku 11.13
pokazano rzeczywiste rezultaty wykonania programu z listingu 11.4.
Listing 11.4. Inicjowanie jednego obiektu za pomoc danych z innego obiektu

 
    
 ; 

 7    0  ! & 
 
!"
;    #$   &  &  &  (   )
;   7   &  &  &  (
/;       0E
    2#   ; -   & & (  !& 
   1  7     ) ! & (
  7         &<&  !
, 
; "";   
  #  
 #  =2%>
1  ##?@AA 3 % 
=$> # $ ,    54  ( 5 ) F ( CD
; "";   7
  #      5 ) &  () 
 #  =2%>  5 0  
1  ##?@AA 3 %   <  0   5
   ,  & ( ()  &   0
; ""/; 
    ,   0      &<&6
  ; ""   2#   ; -    (& 1(
  #   2      5&   5 )E
7  #  = 2 %>    (..  )E ( 
1 ##?@AA 3 %   <  0   5
    & (  . 0)E   
       ( . 0)E   
    F & &
 #  ,      &<&   F &.E
  7 ; ""       4   
    ,
  ; "" 1   =>  ! . 0.
  *%      5
=*%> # $ ,    & 4 & 
 
  




;  +G     + 
;  +?     + 
 

+  # +

  

  & ( CD
 

+  # +

  

  & ( CD

588

Cz II n Programowanie obiektowe w C++

Rysunek 11.13.
Wyjciowy wydruk
programu z listingu 11.4

 2#    "   2# 


 

+  # +

  

  & ( CD
 

+  # +

  

  CD B   1(0


 1+A    1   ! +   !  &  0
 ;  #   ( 
 

+ # +

  

  & ( CD
 1+?     +    ! B  
 

+ # +

  

  & ( CD
 

+  # +

  

 ,   &F (  1& 


 

+  # +

  

  &   !  0


  $
,

Gdy tworzony jest obiekt


klasy
  (a jest on tworzony na stosie, poniewa
jest lokaln
zmienn automatyczn), zostaje mu przyporzdkowana w pamici przestrze wystarczajca
dla zapamitania wskanika typu  / i liczby cakowitej. Nastpnie wywoywany jest konstruktor. Po stronie kodu klienta widzimy znak operatora przypisania  , ale to nie oznacza
operacji przypisania ten symbol oznacza tu zainicjowanie obiektu. Jak ju wspominano
wczeniej, nie ulega adnej wtpliwoci, e po utworzeniu obiektu w pamici5 zawsze nastpi wywoanie konstruktora. Pytanie tylko, ktry spord konstruktorw zostanie wywoany
w danym, konkretnym przypadku. Odpowied brzmi: to zaley od danych, ktre dostarczy
kod klienta wtedy, gdy tworzony jest dany obiekt. Na listingu 11.4 funkcja-klient,  ,
dostarcza konstruktorowi biecy, rzeczywisty argument istniejcy ju obiekt . Skoro tak,
wywoany tu zostanie konstruktor z jednym parametrem, bdcym obiektem takiego samego
typu, jak klasa, do ktrej naley konstruktor w naszym przypadku typu
 .

Tj. przyporzdkowaniu pamici dla samego obiektu przyp. tum.

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

589

Jak nazywa si taki konstruktor z jednym parametrem tego samego typu, co wasna klasa
konstruktora? Jak zapewne pamitasz z rozdziau 9. Klasy w C++ jako jednostki modularyzacji, to jest konstruktor kopiujcy, poniewa jego dziaanie polega na skopiowaniu danych
z jednego obiektu do innego obiektu; ale klasa
  nie zawiera konstruktora kopiujcego. Czy to oznacza, e prba wywoania takiego nieistniejcego konstruktora kopiujcego
spowoduje komunikat o bdzie skadniowym? Nie. Kompilator wygeneruje wywoanie domylnego konstruktora kopiujcego, ktry sam automatycznie doda do specyfikacji klasy.
Kompilator sam dodaje taki konstruktor i ten sam kompilator generuje jego wywoanie. Ten
konstruktor skopiuje pola swojego obiektu-argumentu do nowego obiektu, ktry wanie zosta
utworzony. Dla klasy
  taki dodawany automatycznie przez kompilator konstruktor
kopiujcy wyglda nastpujco:
 &  &  & (.       &  
; "";   ; -
  #   & ( 5 )E !&  & 
 #   ,  & (  &<&  !&  & 

Na rysunku 11.14 pokazano, jak dziaa ten konstruktor. Gdy utworzony zostaje obiekt
klasy

 , jego pole  przybiera warto 9, a jego wskanik 
 jest ustawiany tak, by wskazywa ten sam obszar pamici na stercie, ktry wskazuje wskanik 
 nalecy do obiektu .
Rysunek 11.14.
Diagram pamici przy
inicjowaniu jednego
obiektu klasy String
za pomoc danych
zawartych w innym
obiekcie teje klasy

Podobnie jak w poprzedniej opowieci o przekazywaniu parametrw, te dwa obiekty,


oraz ,
maj jeden wsplny segment pamici na stercie, a nie dwa rne. Ten obszar pamici zosta
wczeniej przydzielony obiektowi , ale teraz sta si wsplny dla niego i obiektu
, a kady
z tych obiektw uwaa, e ten obszar pamici na stercie jest do jego wycznej dyspozycji
(i zachowuje si tak, jakby tak byo). Taka sytuacja jest jeszcze gorsza ni przy przekazaniu
poprzez warto. Przy przekazaniu poprzez warto biecy, rzeczywisty argument wystpuje
w przestrzeni nazw klienta (ang. client scope), a parametr formalny w przestrzeni nazw
serwera (ang. server scope). W kadym, dowolnie wybranym momencie wykonania programu
tylko jeden z tych obiektw moe by widoczny i dostpny. W tym przypadku obydwa obiekty
znajduj si w tej samej przestrzeni klienta i mona si do nich odwoywa, co wicej, obydwa
mog zosta zmodyfikowane w tym samym zakresie widocznoci nazw.

590

Cz II n Programowanie obiektowe w C++


Skoro te dwa obiekty posuguj si tym samym obszarem pamici na stercie, z punktu widzenia kodu klienta obydwa te obiekty s traktowane jako synonimy. Tak wic gdy obiekt

zostaje zmodyfikowany przez kod klienta, obiekt  take zostaje zmodyfikowany. Czy jest
to wyranie widoczne na rysunku 11.13? Z punktu widzenia powszechnej intuicji programistycznej nie ma adnego racjonalnego powodu, by obiekt  mia by zmodyfikowany w kodzie klienta tym niemniej tak si dzieje.
Zastanwmy si nad tym. To wydaje si dziwne tylko z punktu widzenia popularnej intuicji. Na kursach programowania dla pocztkujcych autor czsto spotyka suchaczy, ktrzy
maj kopoty z prostym kodem posugujcym si liczbami cakowitymi:
  # %$  #  # :$  (& (   )E O

Wikszoci programistw wydaje si zupenie oczywiste, e warto  nie zmieni si po


tym, jak zmodyfikowana zostaa warto
, poniewa zmienne
oraz  zajmuj rne obszary pamici. Niektrzy jednak stwierdzaj: OK. Zrobilimy tu najpierw zaoenie, e
zmienne  oraz
oznaczaj to samo. Skoro teraz zmodyfikowalimy warto
, nie ma si
co dziwi, e zmienia si rwnie warto .
I maj racj. Naley im si punkt. Jeli zmienne s synonimami, zmodyfikowanie wartoci
jednej z nich jest widziane przez drug. Jak zapewne pamitasz, to cakiem powszechna
sytuacja, gdy jedna z takiej pary zmiennych jest zwyk zmienn, a druga jest referencj.
  # %$  - #  # :$    (& (  )E O

W tym przykadzie typowa intuicja programisty po prostu nas zawiedzie. Moe dlatego, e
to logika nowicjusza. Poczynilimy zaoenie, e dwie zmienne,
oraz , s takie same. I co?
Teraz lekkie zdziwienie, e stan obiektu  zmienia si po zmianie stanu obiektu
? Teraz
obiekt  zawiera 20. To jest logika, do ktrej musz przywykn wszyscy uytkownicy C++,
i nowicjusze, i eksperci. Co wicej, musz si z ni poczu komfortowo.

Semantyka referencji i semantyka wartoci


W istocie s dwa rodzaje powszechnej programistycznej intuicji, ktre odpowiadaj dwu
rnym koncepcjom informatyki, a s nazywane semantyk wartoci (ang. value semantics)
oraz semantyk referencji (ang. reference semantics). Sowo semantyka jest tu uywane
w znaczeniu kopiowania danych.
Bardziej powszechna intuicja programistyczna posuguje si semantyk wartoci. Kady
obiekt (w nieco szerszym znaczeniu, tj. albo zmienna elementarnego typu wbudowanego,
albo obiekt klasy zdefiniowanej przez programist) posiada swoj odrbn lokalizacj
w pamici (wasny, odseparowany obszar). Przyrwnanie dwch takich obiektw jest rozumiane jako powtrzenie w innym miejscu pamici takiego samego ukadu bitw. W C++
(jak i w wikszoci pozostaych jzykw programowania) taka semantyka wartoci jest uywana i wobec zmiennych elementarnych typw wbudowanych, i wobec obiektw klasy
zdefiniowanej przez programist.
  &  ) B  )E  !0  E %$
  # %$  #  # :$

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

591

Oto dlaczego takie intuicyjne podejcie jest bardziej powszechne. Z takiego punktu widzenia,
gdy dwa obiekty maj t sam warto, maj dwa odrbne zbiory bitw, a zatem zmiana
wartoci jednego z tych obiektw nie moe mie wpywu na istniejcy ju zbir bitw odnoszcy si do drugiego z tych obiektw.
Inna, nieco mniej powszechna intuicja programistyczna, posuguje si semantyk referencji.
Wedug takiego wyobraenia, gdy obiektowi zostaje przypisana warto, otrzymuje on referencj (lub wskanik) do tej wartoci. Przyrwnanie dwch obiektw oznaczaoby zatem
ustawienie ich referencji (lub wskanikw) tak, by odwoyway si do tego samego adresu
w pamici. Jeli tablica znakowa wskazywana przez wskanik w jednym z tych obiektw
si zmieni, drugi z tych obiektw automatycznie dostrzega tak zmian, poniewa obydwa
wskaniki wskazuj t sam lokalizacj w pamici. W C++ taka semantyka referencyjna jest
stosowana dla wskanikw i referencji, przy przekazywaniu parametrw poprzez referencj
lub poprzez wskanik, dla tablic i wobec poczonych poprzez wskaniki struktur danych.
  & 1( B  )E  !0  E :$
  # %$  - #  # :$

Znowu zdziwienie? e semantyka referencji jest mniej powszechna? Jest ona stosowana
gwnie z powodu denia do wyszej efektywnoci (np. pozwala wyeliminowa kopiowanie obiektw przy przekazywaniu parametrw do funkcji). Czasem takie referencyjne dziaania przychodz same bez adnego zaproszenia, jak w tym przypadku, a my powinnimy by
przygotowani na ich rozpoznanie i na waciwe wobec nich postpowanie. Programista piszcy
w C++ powinien zawsze pamita o rnicy pomidzy semantyk wartoci (kopiowania)
a semantyk referencji (wskazania).
To jeszcze nie koniec kopotw z programem z listingu 11.4. Gdy wykonanie programu dochodzi do zamykajcego nawiasu klamrowego zagniedonego bloku instrukcji (zatem i zagniedonego zakresu widocznoci nazw), obiekt
powinien zosta usunity, poniewa jest
zdefiniowany tylko wewntrz tego zakresu (to ulubiony temat autora odnonie dyskusji i analiz
zachowania si kodw). Obiekt  jest zdefiniowany w obrbie caego ciaa funkcji   i powinien by dostpny dla dalszego uytkowania. Na listingu 11.4 autor podejmuje prb wydrukowania zawartoci obiektu  pod koniec kodu funkcji  . Zwr uwag, e ta instrukcja wyprowadzenia danych jest taka sama, jak poprzednia, a te dwie instrukcje rozdziela jedynie
nawias klamrowy zamykajcy zagniedony zakres widocznoci nazw. Powierzchowne wraenie jest takie, e nic przecie nie wydarzyo si pomidzy tymi dwiema instrukcjami w kodzie klienta, zatem te dwie instrukcje powinny da taki sam wydruk wyjciowy. Tak jednak
nie jest. Jeszcze raz tradycyjna intuicja programistyczna okazuje si niewystarczajca do zrozumienia programu napisanego w C++ i musimy rozwin nasz intuicj, by pozwolia nam
na czytanie i zrozumienie takich fragmentw kodu, jak ten.
Jak wida na rysunku 11.14, pierwsza instrukcja wyprowadza czytelny i zrozumiay wydruk.
Nie jest to dokadnie to, czego normalnie mona by si byo spodziewa, ale przynajmniej
jest. Druga instrukcja wyprowadza miecie. Co si wydarzyo pomidzy tymi dwiema instrukcjami? Gdy wykonanie programu doszo do nawiasu klamrowego zamykajcego zakres widocznoci zagniedonego bloku, wobec lokalnego obiektu
zdefiniowanego w tym
zagniedonym bloku zosta wywoany destruktor klasy
 . Jak wida z listingu 11.4
i z rysunku 11.14, ten destruktor, posugujc si operatorem 
, zwolni i zwrci do
systemu pami wskazywan na stercie przez wskanik 
 nalecy do likwidowanego
obiektu
. Ta sekcja dynamicznej pamici w istocie bya przyporzdkowana obiektowi ,

592

Cz II n Programowanie obiektowe w C++


ale system o tym zapomnia. System pamita jedynie, e pami wskazywana przez wskanik 
 powinna zosta zwrcona do systemu zgodnie z kodem destruktora klasy
 .
Obiekt  zosta pozbawiony swojej dynamicznej pamici, ale nikt o tym nie wie. Obiekt 
jest formalnie nadal w zakresie widocznoci i dostpnoci i wydaje si pozostawa w dobrym
zdrowiu. To jednak tylko pozory. Ten obiekt nie moe by ju wykorzystywany do niczego
uytecznego w kodzie klienta.
To przypomina przekazanie parametrw przez warto i podobnie, jak poprzednio, to jeszcze nie koniec caej historii. Gdy wykonanie programu dochodzi do nawiasu klamrowego
zamykajcego kod funkcji  , obiekt  powinien znikn, zgodnie z reguami widocznoci
nazw. Bezporednio przedtem wywoywany jest destruktor, ktry prbuje zwolni i zwrci
do systemu wczeniej ju zwrcon pami na stercie. Taki program jest nieprawidowy. Moe
zrobi, co zechce (nastpi zaamanie si programu).

Konstruktor kopiujcy definiowany przez programist


Podsumowujc krtko zagadnienie dynamicznego zarzdzania pamici: ten problem ma
tylko jedno rozwizanie konstruktor kopiujcy zdefiniowany przez programist. Taki konstruktor powinien przydzieli miejsce w pamici na stercie dla docelowego obiektu komunikatu,
podobnie jak funkcja operatorowa operatora konkatenacji, opisana w poprzednim podrozdziale.
Oto algorytm dziaania takiego konstruktora:
1. Skopiuj dugo tablicy znakowej reprezentowanej przez obiekt parametr do pola
 docelowego obiektu komunikatu.
2. Przyporzdkuj pami na stercie; ustaw wskanik 
 w docelowym obiekcie

komunikatu tak, by wskazywa pocztek tej pamici na stercie.


3. Sprawd, czy przydzia pamici na stercie si powid6. Jeli w systemie zabrako

pamici, zakocz dziaanie.


4. Kopiuj znaki reprezentowane przez obiekt parametr do nowej, przydzielonej

na stercie pamici.
Oto konstruktor kopiujcy zdefiniowany przez programist, stanowicy rozwizanie naszego problemu.
 &  &  & (. 1     0
; "";   ; -

 #   5 )E &  <'5 
 #  =2%>  F. 0!( 0  
1   ## ?@AA 3 %   <  0   5
      & ( & <'5 
,

Tj. czy wskanik wskazuje cokolwiek przyp. tum.

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

593

Zwrmy uwag, e parametr  zostaje tu przekazany poprzez referencj. To jest referencja


do obiektu stanowicego biecy argument dla danego wywoania konstruktora kopiujcego. Przy przekazywaniu argumentu do funkcji nie nastpuje adne kopiowanie pl danych
argumentu. Przeciwnie, dynamiczny przydzia pamici na stercie dla obiektu docelowego
nastpuje w obrbie kodu ciaa tego konstruktora. Zawarto dynamicznej pamici zwizanej z kopiowanym obiektem-argumentem jest kopiowana do dynamicznej pamici przydzielonej obiektowi docelowemu.
Taka technika jest mniej efektywna ni kopiowanie pl obiektu, jak na listingu 11.4. Semantyka wartoci jest powolniejsza od semantyki referencji, poniewa operuje na wartociach,
a nie na referencjach czy wskanikach. Z drugiej jednak strony, semantyka wartoci jest bezpieczna. Przypomnijmy kod klienta, ktry spowodowa wszystkie te problemy.
;  # 
  B !  ! () F  !0 &  &  & (.

Po wykonaniu tego kodu (teraz, po wprowadzeniu konstruktora kopii) wskaniki 


 nalece do dwch obiektw,
oraz , wskazuj dwa rne obszary pamici na stercie.
Jeli pord pl danych klasy znajduje si wskanik (wskaniki), a obiekt danej klasy
posuguje si dynamicznym zarzdzaniem pamici na stercie, projektant takiej klasy
powinien zdecydowa, czy ta klasa wymaga zastosowania semantyki wartoci, czy te
semantyki referencyjnej. Jeli potrzebuje semantyki wartoci i jeli inicjuje jeden obiekt,
posugujc si zawartoci innego obiektu, powinien si upewni, e taka klasa zawiera
zdefiniowany przez programist, wasny konstruktor kopiujcy.

Na listingu 11.5 pokazano program z listingu 11.4, w wersji w ktrej klasa


  definiuje
wasny konstruktor kopiujcy i posuguje si semantyk wartoci do zainicjowania obiektu.
Wydruk wyjciowy tego programu przedstawiono na rysunku 11.15. Jak wida, problem
integralnoci programu znikn. Obiekty
oraz  klasy
  nie s ju wzajemnymi synonimami. Gdy obiekt
si zmienia, obiekt  pozostaje niezmienny. Gdy koczy si zagniedony zakres widocznoci nazw i znika obiekt
, obiekt  istnieje nadal i moe nadal by
uywany w kodzie klienta bez jakichkolwiek trudnoci. Autor radzi przeledzi uwanie zawarto tego listingu i wydruku wyjciowego programu, by mie pewno, e wyranie widzi
si relacje pomidzy tymi dwoma obiektami.
Listing 11.5. Zastosowanie konstruktora kopiujcego do zainicjowania jednego obiektu przy wykorzystaniu
danych z innego obiektu

 
    
 ; 

 7    0  ! & 
 
7     7    1&(  
  7 #  =2%>  5 0    !& 
1 ##?@AA 3 %   () 0    5 B & 
   & ( &   0
   ,  'E  &<&  0  
!"
;    #$   &  &  &  (   )
;   7   &  &  &  (

594

Cz II n Programowanie obiektowe w C++

Rysunek 11.15.
Wydruk wyjciowy
programu z listingu 11.5

;   ; -   &  &  & (.


/;      &      E . 0E
    2#   ; -    5.  (  !&
   1  7   4  )E 0
  7       'E  &<&  !
, 
; "";   
  #  
 #   ++  ,  & (   54   0
; "";   7
  #      5 ) ()  & 
 #     ,   0E & ( &
; "";   ; -  &  &  & (.
  #     5 ) ()  & 
 #       ,   0E & ( &
; ""/; 
    ,   0     &<&6
  ; ""   2#   ; -    (& 1(
  #   2      5&   5 )E
7  #  = 2 %>    (..  )E (   
1 ##?@AA 3 %   <  0   5
    & (  . 0)E   
       ( . 0)E   
    F  )E
 #  ,    &<&   F &.E
  7 ; ""       4   
    ,
  ; "" 1   =>  ! . 0.
  *%      5
=*%> # $ ,    & 4 54 ; 
 

 




;  +G     +   +G (  +
;  +?     +   +?   F '()E <+
 

+  # +

  

    ( CD
 

+  # +

  

    ( CD
 2#     2# 
 

+  # +

  

    ( CD
 

+  # +

  

  CD B   1(0


  " +?  (  H!H+"
 1+A    1   ! +   !  &  0

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

595

 ;  #    5 &  &  & 


 

+ # +

  

  CD B    


 1+?     +    &
 

+ # +

  

  CD B    


 

+  # +

  

 ,   0  5


 

+  # +

  

  &    


  $
,

Na listingu 11.5 klasa


  ma trzy konstruktory, ktre w przyblieniu robi to samo. Wszystkie przydzielaj dynamicznie pami na stercie i inicjuj jej zawarto. W pierwszym konstruktorze konwersji dane pocztkowe (inicjujce) to pusty acuch znakw (tylko znak terminatora zero). W drugim konstruktorze konwersji dane inicjujce to tablica znakowa
dostarczona przez kod klienta w formie biecego argumentu konstruktora. W konstruktorze kopiujcym dane inicjujce to tablica znakowa reprezentowana przez obiekt, dostarczony
jako argument przez kod klienta. Skoro taka tablica znakowa zostaje umieszczona na stercie,
nie musi ona mie wasnej nazwy i mona si do niej odwoywa przy uyciu wskanika

 wskazujcego t tablic. Skoro obiekt-parametr  naley do tej samej klasy
 , co
docelowy obiekt komunikatu, ktry podlega zainicjowaniu, konstruktor kopiujcy ma prawo dostpu do prywatnego pola danych wskanika 
 tego obiektu, posugujc si jego
pen nazw 
.
Jest zreszt naturalne, e rne konstruktory wykonuj podobne algorytmy, poniewa wynikowy obiekt powinien zawsze wyglda niemal dokadnie tak samo, bez wzgldu na to,
ktry z tych konstruktorw by wywoywany wtedy, gdy tworzony by dany obiekt. Jeli klasa
zawiera jeden lub dwa konstruktory, sensowne jest po prostu dokadne powtrzenie kodu,
sowo w sowo. W miar wzrostu liczby zastosowa takich wsplnych algorytmw (i wszystko wskazuje na to, e do koca jeszcze daleko), programici czsto dokonuj ich hermetyzacji poprzez umieszczenie ich wewntrz prywatnych metod, ktre s nastpnie wywoywane
przez rne funkcje (ju czsto publiczne) nalece do tej samej klasy. Taka funkcja z algorytmem powinna by prywatna, poniewa kodu klienta nie interesuj szczegy obsugi pamici przez obiekt. Takie szczegy niskiego poziomu nie powinny zakca realizacji algorytmu kodu klienta ani zaskakiwa programisty tworzcego kod klienta. Tak prywatn
funkcj mona obejrze na listingu 11.5. Gdy ta funkcja kopiuje swj parametr do przydzielonej mu pamici na stercie, wykorzystywany jest zamiast nazwy wskanik  wskazujcy lokalizacj w pamici, poniewa taka dynamicznie tworzona macierz nie ma wasnej nazwy.
7     7     
  7 #  =2%>   0E    !& 
1 ##?@AA 3 %   () 0    5 B & 4
   & ( &   0
   ,  'E  &<&  0  

Kod z listingu 11.5 demonstruje, jak pierwszy konstruktor konwersji przekazuje do funkcji

 (umie na stercie) pusty acuch znakw, drugi konstruktor konwersji przekazuje
do teje funkcji wasny argument, tablic znakow, natomiast konstruktor kopii przekazuje
do funkcji 
 tablic znakow wskazywan poprzez wskanik 
 jego wasnego
argumentu obiektu.
Gdy jeden obiekt inicjuje drugi obiekt, wywoywany jest konstruktor kopiujcy. To nieuniknione. Jest tylko kwestia, jaki konstruktor zostanie wywoany. Jeli klasa nie zawiera
wasnej, indywidualnej wersji konstruktora kopiujcego, kompilator wygeneruje wywoanie
automatycznie dodanego, domylnego konstruktora kopiujcego, ktry skopiuje pola danych

596

Cz II n Programowanie obiektowe w C++


obiektu. Jeli obiekt danej klasy nie wykorzystuje dynamicznej pamici na stercie, to wystarczy. Jeli obiekty wykorzystuj indywidualne segmenty pamici na stercie (semantyka
wartoci), uycie domylnego konstruktora kopiujcego dodawanego automatycznie przez
kompilator stanowi jakby podoenie miny pod integralno aplikacji. Aby uchroni integralno programu, klasa powinna zawiera implementacj wasnego konstruktora kopiujcego, ktry zapewni docelowemu obiektowi przydzia jego wasnej pamici na stercie.
W poprzednim zdaniu okrelenie klasa powinna zawiera implementacj podkrela normalne relacje klient-serwer pomidzy rnymi segmentami kodu programu i podzia na rne
strefy koncentracji uwagi ze strony czowieka. Kod klienta wyraa swoje potrzeby, posugujc si obiektami do realizacji celu aplikacji (w tym np. inicjujc jeden obiekt za pomoc
drugiego obiektu). Kod serwera obsuguje potrzeby kodu klienta poprzez zaimplementowanie
stosownych metod, ktre s wywoywane z kodu klienta. Konstruktory s wywoywane w sposb niejawny, ale to w niczym nie zmienia relacji klient-serwer.
Jeli aplikacja wymaga zastosowania semantyki wartoci (kopii), klasy z dynamicznym zarzdzaniem pamici mog zosta zmuszone do zapewnienia konstruktorw kopiujcych
przeznaczonych do uycia w innych kontekstach, gdy jeden obiekt inicjuje inny obiekt. Jeden
z takich moliwych kontekstw to przekazywanie obiektu jako parametru poprzez warto.
Jeli tylko odpowiednia wersja konstruktora kopiujcego znajduje si na swoim miejscu, nasza
pierwsza wersja funkcji przeciajcej operator konkatenacji, 
 z listingu 11.3,
jest absolutnie w porzdku.
  ; ""   2#   ;      )E
  #   2      5&   5 )E
 7 #  = 2 %>    (..  )E ( 
1 ##?@AA 3 %   <  0   5
    & (  . 0)E   
       ( . 0)E   
    ! F  )E
 #  ,     F ! &E

Gdy wywoywana jest ta funkcja i tworzona jest kopia jej rzeczywistego, biecego argumentu, nastpuje wywoanie konstruktora kopiujcego zdefiniowanego przez programist.
Ten konstruktor kopiujcy przydziela pami na stercie dla parametru formalnego tej funkcji obiektu  klasy
 . Gdy wykonanie tej funkcji dobiega koca i wywoany zostaje
destruktor wobec tego formalnego parametru, przyporzdkowana mu na stercie pami zostaje
zwolniona i zwrcona do systemu, natomiast nie nastpuje zwolnienie ani zwrot pamici na
stercie przydzielonej rzeczywistemu, biecemu argumentowi. Problem integralnoci programu
znika. Pozostaje natomiast problem efektywnoci wykonania takiego kodu. Gdy parametr jest
przekazywany poprzez warto, wywoanie funkcji operatorowej przy wykonaniu operatora
konkatenacji acuchw znakw obejmuje utworzenie obiektu, wywoanie konstruktora kopiujcego, przydzia pamici na stercie, kopiowanie acucha znakw z pamici jednego
obiektu do pamici drugiego obiektu, wywoanie destruktora i zwolnienie pamici na stercie.
Wywoanie poprzez referencj nie wymaga adnej czynnoci z tej listy. Semantyka referencyjna eliminuje obnienie wydajnoci poprzez wykluczenie niekoniecznego kopiowania.
Nie przekazuj obiektw do funkcji poprzez warto. Jeli obiekty zawieraj wewntrz
wskaniki i dynamicznie zarzdzaj pamici na stercie, nie przekazuj takich obiektw
poprzez warto. Jeli ju koniecznie musisz przekazywa takie obiekty poprzez warto,
zdefiniuj konstruktor kopiujcy, ktry eliminuje problem integralnoci aplikacji. Upewnij
si, e takie kopiowanie nie pogorszy efektywnoci dziaania programu.

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

597

Zwrot poprzez warto


Innym kontekstem wymagajcym semantyki wartoci jest zwrot obiektu z funkcji poprzez
warto. Zagadnienie to byo ju dyskutowane w rozdziale 10. w odniesieniu do klas, ktre
nie obsuguj dynamicznego zarzdzania pamici. Poniewa problem zwrotu obiektu z funkcji polega dokadnie na tym samym, co problem inicjowania jednego obiektu przez inny
obiekt, tylko to krtko przypomn.
Na listingu 11.6 pokazano jeszcze jedn wersj klasy
 . Wewntrz kodu kadego z konstruktorw umieszczono wydruk komunikatw w celu ledzenia wykonania. Dodano funkcj
dokonujc przecienia operatora porwnania zaimplementowan jako metoda naleca do
klasy. Oprcz tego dodano po stronie klienta globaln funkcj 
0
 (wprowad dane)
i uproszczono kod funkcji  . Ten program prosi uytkownika o wpisanie nazwy miasta
i poszukuje wprowadzonej nazwy we wasnej bazie danych. Dla uproszczenia t baz danych zakodowano w sposb sztywny w obrbie funkcji   w formie tablicy zoonej
z acuchw znakw i uyto najprostszego wyszukiwania sekwencyjnego do odnajdywania
miasta podanego przez uytkownika. Wydruk wyjciowy demonstrujcy wyniki wykonania
tego programu zamieszczono na rysunku 11.16.
Listing 11.6. Zastosowanie konstruktora kopiujcego do zwrotu obiektu z funkcji

 
    
 ; 

 7    0   & 
 
7     7  1&(  
  7 #  =2%>  5 0   B  !& 
1 ##?@AA 3 %   () 0    5 B & 4
   & ( &   0
   ,  'E  &<&  &   
!"
;    #$   &  &  &  (   )
;   7   &  &  &  (
;   ; -   &  &  & (.
/;      ( . 0E
    2#   ; -   & & (
   1  7   4  )E !
!    ##   ; -      '(  )E
  7       'E  &<&  !
, 
; "";   
  #  
 #   ++   & (   54 &'   0
 

+ P . & " H+

+HJ+ ,
; "";   7
  #      5 ) ()  & 
 #       0E & ( &
 

+ @   " H+

+HJ+ ,

598

Cz II n Programowanie obiektowe w C++

Rysunek 11.16.
Wydruk wyjciowy
programu z listingu 11.6

; "";   ; -  &  &  & (.


  #     5 ) ()  & 
 #         0E & ( &
 

+ ;&  " H+

+HJ+ ,
; ""/; 
    ,     ( 0  
  ; ""   2#   ; -    (& 1(
  #   2      5&   5 )E
7  #  = 2 %>   (.  )E (   
1 ##?@AA 3 %    0   5 O
    & (  . 0)E   
       ( . 0)E   
    F  )E
 #  ,     F ! &E
!  ; ""  ##  ; -     '(  )E
       ##$ ,    $ () & 
  7 ; ""       4   
    ,
  ; "" 1   =>  ! . 0.
  *%      5
=*%> # $ ,  5  & 4 54 &'
;   Q 
  

+ R (    &" +  & & "   


  =:$$>   .  )E 0
      (   F & &
  ;    ,   5( &  & 
 

  SNT # 8, 
;   =8>  !  ! 5 F   !& '
 7=8> #  +N  + +U + +V + +Q+ ,
1   (#$ (
SNT (22
  =(> 2# =(> ,   B  =(>  2#=(> 
;   #  Q    &   1 ! &  & 
 
1  #$ 
SNT 22   &    0 .
 1  => ##  !& ,  ( () 5)
1  ## SNT  ()  5)   5)  & 4
 

+ S +

  

+   5  J+


 

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

599

 

+ S +

  

+  5  J+
  $
,

Gdy w funkcji   zostaje utworzona tablica skadajca si z obiektw, dla kadego z elementw tej tablicy nastpuje wywoanie domylnego konstruktora zawartego w specyfikacji
klasy
  (to znaczy pierwszego konstruktora konwersji z domyln wartoci argumentu). Ten konstruktor umieszcza w pamici pusty acuch znakw o zerowej dugoci i wyprowadza na ekran komunikat Zapocztkowany. Gdy wywoywana jest funkcja operatorowa

, aby doczy na zasadzie przyrostkowej (ang. append) nazwy miast do zawartoci reprezentowanej przez poszczeglne obiekty, tablica znakowa (tj. acuch znakw) jest
przekazywana, jako parametr, do funkcji operatorowej realizujcej przecienie operatora. .
Przeciony operator oczekuje parametru typu
 , zatem w tym momencie wywoany
zostaje drugi spord konstruktorw konwersji i ten wanie konstruktor wyprowadza na
ekran komunikat 1Utworzony dla kadego obiektu elementu tablicy.
Nastpnie wywoana zostaje funkcja 
0
. Prosi ona uytkownika o wpisanie nazwy
miasta. Wczytuje i zapamituje dane od uytkownika i przekazuje wpisan nazw miasta jako
argument do konstruktora konwersji klasy
 . Z tego powodu na ekranie ponownie widzimy komunikat -
 ! wyprowadzony przez ten konstruktor konwersji. Poniewa obiekt
jest jedynym obiektem klasy
  w obrbie funkcji  , gdy zostaje wywoana funkcja

0
, wywoanie konstruktora konwersji wewntrz kodu funkcji 
0
 nastpuje
w odniesieniu do obiektu jako wywoanie konstruktora dla tego obiektu. Konstruktor kopiujcy nie zostaje wywoany. Nawet jeli obiekty klasy
  dynamicznie zarzdzaj pamici, integralno programu jest tu chroniona. Konstruktor kopiujcy nie ma tu nic do rzeczy
odnonie implementacji semantyki wartoci. Taki konstruktor konwersji ma za zadanie przydzieli obiektowi w funkcji   jego wasn, odrbn pami na stercie. Tak jak
w dowcipie o mapie i krokodylu. Krokodyl chce to gra na pianinie. Krokodyl chce to
piewa. A mapa nie ma tu nic do rzeczy.
Bymy poczuli si bardziej komfortowo posugujc si obiektami, ktre dynamicznie zarzdzaj pamici, wprowadzimy w obrbie funkcji 
0
 niewielk modyfikacj
dodamy tylko jeden lokalny obiekt sucy nam do przechowywania danych wprowadzonych
przez uytkownika.
;   Q 

 

+ R (    &" +


  =:$$>   . 0
         F & &
;  3 #    &  &  &  (
  3 ,  &  &  & (.

Zmiana jest niewielka. Gdyby  by zmienn typu wbudowanego, w ogle nie byoby o czym
mwi. W przypadku obiektw z dynamicznym zarzdzaniem pamici pojawia si tu zupenie inny problem. Gdy utworzony zostaje lokalny obiekt , wywoywany jest wobec niego
konstruktor konwersji. Gdy natomiast funkcja koczy swoje dziaanie, obiekt w kodzie funkcji   zostaje zainicjowany przy uyciu konstruktora kopiujcego. Jeli nie zosta zaimplementowany konstruktor kopiujcy zdefiniowany indywidualnie przez programist, stosowany jest tu domylny konstruktor kopiujcy dodany automatycznie przez kompilator.
Ten konstruktor kopiuje pola danych obiektu  na pola danych obiektu , ale nie dokonuje

600

Cz II n Programowanie obiektowe w C++


przydziau pamici na stercie. Wskaniki 
 zawarte w obiektach  oraz wskazuj ten
sam adres pamici na stercie. Gdy koczy si dziaanie funkcji 
0
, a obiekt  zostaje usunity, wywoywany jest wobec niego destruktor klasy
 , ktry zwalnia pami na stercie wskazywan przez wskanik 
 obiektu  i zwraca j do systemu. Oznacza
to, e obiekt narodzi si jako upoledzony od urodzenia jego pami na stercie zostaa
zwolniona w chwili, gdy ten obiekt powsta.
Jakie s tego konsekwencje? Takie same, jak poprzednio. Komputer autora si zawiesi. Moe
si okaza, e komputer czytelnika bdzie dziaa nadal, ale to wszystko kwestia przypadku.
Program jest nieprawidowy. Ten program wymaga zastosowania konstruktora kopiujcego
zdefiniowanego przez programist.
Gdy dostarczony zostanie konstruktor kopiujcy zdefiniowany przez programist, wszystko
odbywa si poprawnie. Przykadowy wydruk wyjciowy przedstawiajcy rezultaty dziaania
takiego programu pokazano na rysunku 11.17.

Rysunek 11.17.
Wydruk wyjciowy
programu z listingu 11.6
ze zmodyfikowan wersj
funkcji enterData()
oraz z konstruktorem
kopiujcym

Te, wspomagajce ledzenie programu, wydruki komunikatw wyjciowych pokazuj, e po


wprowadzeniu przez uytkownika wiersza danych wejciowych (ang. input line) nastpuje
wywoanie konstruktora konwersji wobec lokalnego obiektu  w obrbie kodu ciaa funkcji

0
, a nastpnie wywoany zostaje konstruktor kopiujcy wobec lokalnego obiektu
w obrbie kodu funkcji  . Krokodyl gra na pianinie, a mapa piewa. Ta wersja jest nieco
powolniejsza ni poprzednia, ale nie to jest tu najwaniejsze. To, co jest rzeczywicie istotne,
to fakt, i ta wersja zachowuje si inaczej ni poprzednia. Jeszcze waniejsze jest to, e
gdyby zmienne  oraz byy zmiennymi typu elementarnego, taka zmiana nie miaaby wpywu
na zachowanie si programu. No i tak to jest z naszym dowiadczeniem z typami wbudowanymi, ktre uksztatowao nasz intuicj programisty. Pomimo wszystkich tych wysikw,
elementarne typy wbudowane i typy danych definiowane przez programist s traktowane
w C++ w sposb rny. Praca z obiektami wymaga zmian w intuicji programisty. Z tego
powodu autor powici tyle czasu na wyliczanie tych sekwencji zdarze, by pomc czytelnikowi w rozwoju takiej nowej intuicji programisty. Czytelnik powinien zyska pewno, e czuje
si swobodnie w zagadnieniach wystpujcych na styku kodu klienta z metodami nalecymi
do klas, ktre s wywoywane w sposb niejawny.

Ograniczenia skutecznoci konstruktorw kopiujcych


Jestemy ju prawie u celu. Autor chciaby tu dokona jeszcze jednej niewielkiej zmiany
w programie, tym razem po stronie kodu klienta. Zamiast definiowa obiekt w obrbie funkcji

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

601

  i natychmiast go inicjowa, obiekt ten zdefiniuje si przy uyciu konstruktora do-

mylnego, a nastpnie zostan mu przyporzdkowane te dane, ktre wprowadzi uytkownik


w trakcie wykonania kodu wywoanej funkcji 
0
.
 

  SNT # 8,   !   
 ;   #  Q  
* ! &  &  & (. 0  
;    &  &   )
 #  Q     6    F &  &  & (.
 ( B  &    &   '
  $
,

Po wprowadzeniu tej zmiany system na komputerze autora si zawiesi. Mona sobie oszczdzi ogldania jeszcze jednego okienka dialogowego z bezuytecznymi informacjami o przyczynach tego problemu. W kocu to tylko przykadowe wykonanie na konkretnym komputerze
w konkretnym systemie operacyjnym. Istotne jest to, e ten program jest nieprawidowy.
Nawet jeli kompilacja jego kodu przebiega poprawnie, jego zachowanie pozostaje nieprzewidywalne i taki program nie moe by uruchamiany. Skoro kompilator nie powiedzia nam,
e ten program jest bdny, nasza intuicja programisty powinna nam pomc w zrozumieniu
tego, co w niejawny sposb odbywa si w tle podczas wykonania tego programu.

Przecienie operatora przypisania


Przy wielu rnych okazjach powtarzano tu, e w C++ zainicjowanie obiektw i przypisywanie wartoci obiektom to dwie rne rzeczy. Gdy mamy do czynienia z elementarnymi,
wbudowanymi typami danych, taka rnica ma czsto znaczenie czysto akademickie. Na
przykad rozwamy nastpujcy fragment kodu klienta:
  # K
  #     ( ( 

i porwnajmy go z nastpujcym fragmentem:


  # K
 
 #      ) ( 

W pierwszym przykadzie zmienna jest inicjowana w momencie deklaracji (jest to zatem


jednoczenie definicja tej zmiennej). W przypadku zmiennych typw elementarnych kocowy rezultat bdzie taki sam. Jeli natomiast takie zmienne bd obiektami typu zdefiniowanego przez programist, ktre obsuguj wasn pami, rnica stanie si istotna.
;   # +I + ;   #   !&  ( 
;   # +I + ;    #   !&   

602

Cz II n Programowanie obiektowe w C++


Pierwszy wiersz podanego kodu moe postawi nas w kopotliwej sytuacji, jeli dana klasa
nie bdzie zawiera konstruktora kopiujcego. Drugi wiersz podanego kodu natomiast okae
si problematyczny, jeli klasa nie bdzie zawiera funkcji dokonujcej przecienia operatora
przypisania. W drugim wierszu nie bdzie wywoywany konstruktor kopiujcy.

Problem z dodan przez kompilator obsug operatora przypisania


Jeli klasa zawiera funkcj dokonujc wobec niej przecienia operatora przypisania, to ta
funkcja zostanie wywoana do obsugi operatora w drugim wierszu podanego fragmentu kodu
klienta. Jeli natomiast klasa nie zawiera przecienia operatora przypisania, kompilator
automatycznie doda wasn, domyln funkcj operatorow, wykonujc t operacj wobec
obiektw danej klasy. Taka funkcja operatorowa jest bardzo podobna do konstruktora kopiujcego. Funkcja kopiuje pola danych (zawarto) obiektu stanowicego prawostronny operand
operatora przypisania do pl danych obiektu stanowicego lewy operand operatora przypisania.
Podobnie jak w przypadku dodawanego automatycznie przez kompilator domylnego konstruktora kopiujcego, ta dodawana przez kompilator funkcja dokonujca przecienia operatora przypisania jest zawsze dostpna. Dla tych klas, ktre nie wykonuj dynamicznego zarzdzania pamici (np. takich klas, jak , 
 , 
 itp.), taka automatycznie
dodana funkcja operatorowa przeciajca operator przypisania jest odpowiednia. W przypadku tych klas, ktre zarzdzaj pamici w sposb dynamiczny, taki dodany przez kompilator operator przypisania powoduje kopoty.
Gdy wobec obiektw klasy
  wykonywana jest operacja przypisania, pola danych s
kopiowane metod pole po polu. Wskanik 
 zawarty w obiekcie znajdujcym si po lewej
stronie operatora przypisania wskazuje t sam lokalizacj w pamici, co wskanik 
 zawarty
w obiekcie znajdujcym si po prawej stronie operatora przypisania. Takie dwa obiekty
staj si synonimami. Jeli zmodyfikujemy jeden z tych obiektw, powiedzmy obiekt , zmiana
ta jest widziana przez drugi z tych obiektw, w tym przypadku obiekt .
Gdy jeden z tych obiektw jest usuwany poprzez reguy widocznoci nazw (ang. scope rules)
lub poprzez operator 
 (np. obiekt ), wobec tego obiektu zostaje wywoany destruktor
i pami wskazywana poprzez wskanik 
 zawarty w tym obiekcie zostaje zwolniona. W rezultacie drugi z tych obiektw (w tym przypadku ) zostaje pozbawiony przyporzdkowanej mu dynamicznie na stercie pamici, nawet jeli po stronie kodu klienta wydaje si pozostawa zupenie normalnym obiektem. Wszelka prba uycia takiego obiektu stanowi bd.
Gdy ten obiekt take ma zosta usunity, zostaje wobec niego wywoany destruktor, ktry prbuje zwolni pami na stercie wskazywan przez wskanik 
 tego obiektu. Ale przecie
ta pami zostaa ju zwolniona! Jak ju wyjaniono wczeniej, prba zwolnienia pamici,
ktra ju uprzednio zostaa zwolniona, powoduje wytworzenie stanu, w ktrym dalsze
dziaanie programu staje si nieprzewidywalne. Nawet jeli nie jest to bd skadniowy jest
to nieprawidowe z semantycznego punktu widzenia.
Wyledzenie przyczyny takiego problemu jest trudne, poniewa nie ma tu bezporedniego
odniesienia do rezultatw dziaania programu, a istnienie indywidualnego konstruktora kopiujcego nie jest w stanie zapobiec temu problemowi, poniewa wtedy gdy wykonywana
jest operacja przypisania, nie nastpuje wywoanie adnego konstruktora. W C++ przypisanie i zainicjowanie to nie jest to samo.

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

603

Przecienie przypisania wersja pierwsza (z wyciekiem pamici)


Rozwizaniem tego problemu jest dokonanie przecienia operatora przypisania dla danej
klasy. Taki poddany przecieniu operator przypisania musi si upewni, e obiekty stanowice
lewostronny i prawostronny operand nie przestay wskazywa tego samego obszaru pamici
na stercie.
Wbudowany operator przypisania w C++ jest operatorem dwuargumentowym, z dwoma operandami lewostronnym (docelowym) i prawostronnym (rdowym). Tak samo musi by
w przypadku przecionego operatora przypisania zdefiniowanego przez programist. Skoro tak, interfejs takiej funkcji operatorowej jest podobny do interfejsu konstruktora kopiujcego. Obiekt bdcy operandem lewostronnym jest docelowym obiektem komunikatu, natomiast obiekt bdcy operandem prawostronnym jest parametrem funkcji operatorowej.
 #     5"   # 

Oznacza to, e przeciony operator przypisania, ktry jest nam potrzebny dla klasy
 ,
powinien mie nastpujcy interfejs:
   1&( .F(.(    "
  ; ""   #   ; - 

Funkcja dokonujca przecienia operatora przypisania powinna skopiowa z obiektu stanowicego jej biecy argument pola danych, poza wskanikiem, do obiektu docelowego komunikatu, a nastpnie przydzieli na stercie wystarczajc ilo miejsca w pamici, by skopiowa z pamici na stercie przyporzdkowanej obiektowi bdcemu argumentem funkcji dane
do pamici przydzielonej obiektowi docelowemu. Te dziaania przypominaj sposb postpowania konstruktora kopii:
1. Skopiuj dugo macierzy znakowej reprezentowanej przez obiekt parametr
do pola  docelowego obiektu komunikatu.
2. Przyporzdkuj pami na stercie; ustaw wskanik 
 w docelowym obiekcie komunikatu

tak, by wskazywa pocztek tej pamici na stercie.


3. Sprawd, czy przydzia pamici na stercie si powid7. Jeli w systemie zabrako

pamici, zakocz dziaanie.


4. Kopiuj znaki reprezentowane przez obiekt-parametr do nowej, przydzielonej

na stercie pamici.
Jeli trzeba przypisa jeden obiekt drugiemu obiektowi, a obiekty te dokonuj dynamicznego zarzdzania pamici na stercie, upewnij si, e klasa zawiera funkcj dokonujc przecienia operatora przypisania. Sam konstruktor kopiujcy w tej sytuacji
nie wystarczy.

Tj. czy wskanik wskazuje cokolwiek przyp. tum.

604

Cz II n Programowanie obiektowe w C++


Oto wersja funkcji dokonujcej przecienia operatora przypisania implementujca podany
algorytm. Cho dziaa wolniej ni dodawany przez kompilator domylny operator przypisania, zachowuje semantyk wartoci i powoduje, e obydwa obiekty poddane dziaaniu tego operatora pozostan wzajemnie niezalene.
  ; ""   #   ; -
  #   & (   !  &<&
 #  = 2 %>   (   
1   ## ?@AA 3 %   <  0   5
     ,  & (    0

To cakiem sympatycznie zaimplementowany operator przypisania, traktuje on jednak docelowy obiekt komunikatu dokadnie tak samo, jak czyni to konstruktor kopiujcy zupenie
tak, jakby obiekt wyszed wanie z fabryki i nie mia adnej historii. To cakiem uprawnione
podejcie w przypadku konstruktora kopiujcego, ale to nie jest typowa sytuacja, z ktr
miewa do czynienia operator przypisania. Obiekt docelowy zosta utworzony wczeniej.
Oznacza to, e wtedy, gdy ten obiekt zosta utworzony, wywoywany by wobec niego ktry
z konstruktorw i w trakcie wykonania kodu tego konstruktora wskanik 
 nalecy do
tego obiektu zosta ju raz ustawiony tak, by wskazywa pewien adres pamici na stercie.
Operator przypisania pomija i lekceway t przyporzdkowan na stercie pami. Operator
ustawia wskanik 
 tak, by wskazywa inn lokalizacj w pamici na stercie. Przez to pami
przyporzdkowana temu obiektowi poprzednio zostaje zgubiona (nie jest stosowana, a nie
zostaje zwrcona do systemu). Taki operator przypisania powoduje wyciek pamici. To drugi
rodzaj niebezpieczestwa czyhajcego w programach pisanych w C++ oprcz ryzyka zwalniania dwukrotnie tej samej pamici.
Jaki jest na to sposb? W przeciwiestwie do konstruktora kopiujcego, funkcja dokonujca przecienia operatora przypisania musi zwolni zasoby (tu pami), ktrych uywa
docelowy obiekt danej operacji przypisania, zanim rozpoczo si wykonanie biecej operacji. Ten brak mona stosunkowo atwo uzupeni. Trzeba tylko wiedzie, e naley to
zrobi. Oto ulepszona wersja tej samej funkcji dokonujcej przecienia operatora przypisania.
     ( ( ( 1&(   ("
  ; ""   #   ; -
          !E &  &  & (.
 #   & (   !  &<&
 #  = 2 %>   5 . 0E
1   ## ?@AA 3 %   <  0E  
     ,  & (    0

Przecienie przypisania wersja nastpna (samoprzypisanie)


Podany kod operatora przypisania jest waciwy i adekwatny. Bdzie obsugiwa nasze operatory przypisania w kodzie klienta w sposb poprawny w znakomitej wikszoci przypadkw. Jest tu jednak pewien problem, ktrego zapewne czsto nie bierze si pod uwag. Taka
funkcja operatorowa nie jest w stanie poprawnie obsuy wyraenia przypisania, zapisanego
w kodzie klienta w nastpujcy sposb:
 #     #  0  ! 0  ) &  O

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

605

To cakowicie bezuyteczna instrukcja, ale jest to w peni legalna konstrukcja C++ dla
zmiennych elementarnych typw wbudowanych. Nie ma adnego powodu, by nie moga to
by rwnie konstrukcja legalne dla zmiennych typw zdefiniowanych przez programist.
W istocie jest cakowicie legalna i kompilator nie zasygnalizuje tej instrukcji jako bdu skadniowego. Tyle tylko, e pierwsza instrukcja z kodu naszej funkcji operatorowej 

zwalnia pami na stercie przyporzdkowan obiektowi-argumentowi. Gdy dochodzi do wywoania funkcji bibliotecznej 
!, funkcja ta bdzie w istocie kopiowaa znaki z nowej, przyporzdkowanej wanie na stercie pamici do tej samej pamici. Rezultat takiego
kopiowania zawartoci pomidzy wzajemnie nakadajcymi si obszarami pamici (ang.
overlaped memory areas) jest nieprzewidywalny. I oto mamy jeszcze jeden powd do blu
gowy. Jednak nawet gdyby taki rezultat by cakowicie przewidywalny, poprzednia zawarto pamici na stercie przyporzdkowanej naszemu obiektowi i tak przepada bez wieci.
Jakby si to nie wydawao dziwne, takie przypisanie obiektu samemu sobie nie jest a tak
rzadkoci. Takie operacje czsto wystpuj w algorytmach sortowania i w algorytmach manipulujcych wskanikami. Aby zapobiec prbie kopiowania zawartoci pamici do tego
samego obszaru pamici, funkcja operatorowa moe najpierw sprawdzi, czy referencja do
obiektu-argumentu wskazuje w pamici to samo miejsce (adres), gdzie zlokalizowany jest
docelowy obiekt komunikatu. Dobrym sposobem odwoania si do lokalizacji docelowego
obiektu komunikatu moe by posuenie si wskanikiem
 .
     &   !   0  
 '! &   +  !   !+
  ; ""   #   ; -

1 - ##     () +  ! 5+ B  (
       ! &  &  & (.
 #   &     !  &<&
 #  = 2 %>   5 . 0E
1   ## ?@AA 3 %   <  0   5
      & (    0
,

Taki test mona oczywicie przeprowadzi po stronie kodu klienta przed wywoaniem funkcji
operatorowej dokonujcej przecienia operatora przypisania, ale takie postpowanie spowodowaoby w rezultacie przenoszenie odpowiedzialnoci w gr, do kodu klienta, zamiast
w d, do kodu serwera.
Jeszcze jednym rozwizaniem moe tu by sprawdzenie, czy wskaniki 
 nalece odpowiednio do obiektu-argumentu i do docelowego obiektu komunikatu wskazuj ten sam obszar
pamici na stercie. Taki test w kodzie funkcji operatorowej powinien wyglda nastpujco:
    !   O
1   ##    

Obydwa te rodki zapobiegawcze s rwnowane, ale z niewiadomych powodw ten pierwszy sposb stosowany jest czciej. Powodem moe by to, e wskanik
  ma dla programistw pracujcych w C++ pewne dodatkowe walory estetyczne.

Przecienie przypisania jeszcze jedna wersja (wyraenia acuchowe)


Ta wersja poddanego przecieniu operatora przypisania dziaa poprawnie i powinna by
stosowana we wszystkich klasach, ktre w sposb dynamiczny zarzdzaj swoj pamici

606

Cz II n Programowanie obiektowe w C++


na stercie i wymagaj obsugi operacji przypisania. Mimo to taka wersja operatora przypisania
nie nadaje si do obsugi wyrae acuchowych, w ktrych warto zwracana przez operator
przypisania (tj. funkcj operatorow) zostaje wykorzystana w nastpnej operacji przypisania.
 1&(     ! 5F & F4 (&   "
#  # 

Nie jest do koca jasne, na ile istotna jest obsuga acuchowych operacji przypisania. W kocu zawsze moemy w kodzie klienta zastosowa sekwencj operacji przypisania z zastosowaniem operatora dwuargumentowego.
 #      "   # 
#      "   # 

Tu jednak take caa istota zagadnienia sprowadza si do problemu jednakowego traktowania zmiennych elementarnych typw wbudowanych i zmiennych typw zdefiniowanych przez
programist. W przypadku zmiennych elementarnych typw wbudowanych stosowanie w kodach C++ wyrae acuchowych jest dopuszczalne. Skoro tak, powinno to by take dopuszczalne w kodach C++ wobec zmiennych typw definiowanych przez programist.
Operator przypisania jest prawostronnie czny, zatem znaczenie wyraenia acuchowego
jest nastpujce:
#  #       #    #  

Oznacza to, e funkcja operatorowa przeciajca operator przypisania musi zwrci warto,
ktra jest odpowiednia, by moga zosta uyta jako biecy argument w nastpnym wywoaniu tej samej funkcji operatorowej (innymi sowy, w kolejnym komunikacie). To z kolei
oznacza, e taka funkcja operatorowa powinna zwrci warto (obiekt) takiego typu, jak
klasa, do ktrej naley dana funkcja operatorowa.
;  ; ""   #   ; -   !&

1 - ##    7       +  ! 5.+
       ! &  &  & (.
 #   & (   !  &<&
 #  = 2 %>   0E  
1   ## ?@AA 3 %   <  0   5
      & (    0
  7  
,

Na listingu 11.7 pokazano zmodyfikowan wersj programu z listingu 11.6. Dodano funkcj dokonujc przecienia operatora przypisania. Ta metoda operatorowa posuguje si
wywoaniem prywatnej metody 
 (dos. przydziel pami, rozmie w pamici),
by zada miejsca w pamici na stercie i sprawdzi, czy ta prba przydziau pamici zakoczya si powodzeniem. Aby zmniejszy objto diagnostycznych wydrukw wyjciowych, z wntrza kodu konstruktora domylnego usunito instrukcj wyprowadzania komunikatu 2 3
# !. Zamiast tego dodano wydruk komunikatu 4!  !, ktry bdzie
wyprowadzany na ekran zawsze, gdy nastpi wywoanie funkcji operatorowej przeciajcej operator przypisania. Usunito z kodu take wywoanie operatora konkatenacji w ptli
programowej po stronie kodu klienta, ktra adowaa zawarto do bazy danych, i zastpiono to wywoanie operatorem przypisania. Wydruk wyjciowy tego programu pokazano na
rysunku 11.18.

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

607

Listing 11.7. Klasa String z przecionym operatorem przypisania



 
    
 ; 

 7     &  ! & 
 
7     7     
  7 #  =2%>  5 0  !& 
1 ##?@AA 3 %   () 0    5 B  (
   & ( &   0
   ,    &<&  0  
!"
;    #$   &  &  &  (   )
;   7   &  &  &  (
;   ; -   &  &  & 
/;      &  B   ( 0
    2#   ; -   & & (
;    #   ; -      
   1  7     ) !
!    ##   ; -      '  )
  7         &<&  !
, 
; "";   
  #  
 #   ++  ,  & (   54   0
; "";   7
  #      5 ) &  () 
 #       0E & ( &
 

+ @   " H+

+HJ+ ,
; "";   ; -  &  &  & 
  #     5 ) &  () 
 #         0E & ( &
 

+ ;&  " H+

+HJ+ ,
; ""/; 
    ,   0  
  ; ""   2#   ; -    (& 1(
  #   2      5&   5 )E
7  #  = 2 %>  5  (.(  ) ( 
1 ##?@AA 3 %   <  0   5
    & (  . 0)E   
       ( . 0)E   
    F  )E
 #  ,     F &.E
;  ; ""   #   ; -
 1 - ##    7     +  ! 5+O
       ! &  &  & (.
 #   & (   !  &<&
 #         0E & ( &
 

+ R " H+

+HJ+    )


  7   ,  'E !&     &  & 

608

Cz II n Programowanie obiektowe w C++

Rysunek 11.18.
Wydruk wyjciowy
programu z listingu 11.7

!  ; ""  ##  ; -     '  )


       ##$ ,   $ () & 
  7 ; ""          .
    ,
  ; "" 1   =>  ! . 0.
  *%      5
=*%> # $ ,    & 4 54 &'
;   Q 
  

+ R (    &" +    F & &


  =:$$>  !  .
      (     F & &
  ;    ,  &  &  &  (
 

 




  SNT # 8, 
;   =8>   5 F   !& ' ! 
 7=8> #  +N  + +U + +V + +Q+ ,
1   (#$ (
SNT (22
  =(> # =(> ,   "
  =(>  #=(> 
;    
  0(. F   
    5 &  &  & 
 #  Q  
1  #$ 
SNT 22
 1  => ##  !& ,   &( & 
  =>  ##
1  ## SNT
 

+ S +

  

+   5  J+   


 
 

+ S +

  

+  5  J+  (
  $
,

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

609

Jak wida, problem integralnoci programu znikn. Moemy postpowa z obiektami klasy

  dokadnie w taki sam sposb, jak ze zmiennymi elementarnych, wbudowanych typw
numerycznych. Moemy utworzy takie obiekty bez ich zainicjowania, moemy je zainicjowa,
posuywszy si do tego celu macierz znakow, moemy take zainicjowa takie obiekty,
posugujc si innym, utworzonym wczeniej, obiektem tego samego typu
 . Moemy
jeden obiekt klasy
  przypisa operatorem  drugiemu obiektowi klasy
  tak,
jakby byy to liczby. Zwr uwag, e C++ nie pozwoli na to w odniesieniu do macierzy.
Macierze w C++ posuguj si semantyk referencji, a nie semantyk wartoci.
Moemy do klasy
  doda tyle funkcji przeciajcych operatory arytmetyczne, ile
tylko si zmieci (dodawanie obiektw klasy
 , odejmowanie, mnoenie itp.). Powinnimy
jednake pamita tu o losie programisty serwisanta kodw. Nie powinnimy doprowadza
do tego, by zadanie polegajce na zrozumieniu naszych kodw byo trudniejsze, ni jest to
rzeczywicie konieczne.
Wbudowana w C++ moliwo przeciania operatorw stanowi znaczcy wkad do estetyki
programowania komputerw.
Elastyczno C++ ma swoj cen. Jak zawsze co za co. Jeli chcemy zainicjowa jeden obiekt, posuywszy si w tym celu innym obiektem (przy definiowaniu obiektu, przy
przekazywaniu go jako parametru poprzez warto lub przy zwrocie obiektu poprzez warto
z funkcji), powinnimy zdecydowanie doda do klasy konstruktor kopii. Jeli chcemy dokona
przypisania jednego obiektu innemu obiektowi, powinnimy zdecydowanie doda do specyfikacji klasy funkcj operatorow dokonujc przecienia operatora przypisania.
Problemy z zachowaniem integralnoci programu, ktre mog wystpi z powodu stosowania nieklarownego dynamicznego zarzdzania pamici, s na tyle niebezpieczne, e wielu
programistw implementuje konstruktor kopiujcy i funkcj operatorow operatora przypisania dla kadej klasy, ktra dynamicznie zarzdza pamici. Programici robi to czsto nawet w takich klasach, ktre nie zarzdzaj pamici w sposb dynamiczny. W kocu napisanie
tych funkcji nie wymaga wiele wysiku zawsze lepiej to zrobi, choby tylko tak, na
wszelki wypadek.
Autor uwaa, e to jest problem z kategorii dmuchania na zimne. Zamiast dodawa do kodu
wiele kompletnie bezuytecznych funkcji, projektant powinien uwanie przeanalizowa rzeczywiste wymagania kodu klienta i zrozumie konsekwencje rnorodnych decyzji projektowych.
Z dostarczaniem klas z iloci metod przewyszajc t, ktrej klasa rzeczywicie potrzebuje, wi si liczne problemy. Jednym z nich jest rozdty ponad miar projekt. To nie jest
mao znaczca konsekwencja. Gdy serwisant kodw (lub programista piszcy kod klienta)
przeglda kody bezuytecznych funkcji, nie zwraca uwagi na inne istotne szczegy.
Inn kwesti jest tu efektywno dziaania kodu. Jak wida na rysunku 11.18, problem ten
moe sta si zupenie realny. Dla kadego przypisania wejciowego acucha znakw w obrbie ptli programowej potrzebne s dwa wywoania funkcji oraz wywoanie funkcji operatorowej przeciajcej operator przypisania:
1. Wywoanie konstruktora konwersji wobec argumentu funkcji operatorowej 
.
2. Wywoanie samej funkcji operatorowej 
.

610

Cz II n Programowanie obiektowe w C++


3. Wywoanie konstruktora kopii w celu zwrcenia obiektu poprzez warto z wntrza

funkcji operatorowej.
Pomimo tych wysikw nadal wystpuj znaczne rnice pomidzy traktowaniem obiektw
klas definiowanych przez programist a traktowaniem zmiennych typw wbudowanych.
Gdyby tablice 
56 oraz 56 skaday si z elementw typu elementarnego, wewntrz naszej
ptli programowej mogaby si znajdowa tylko jedna, pojedyncza instrukcja. Przy takiej
konstrukcji klasy
  wntrze tej ptli programowej reprezentuje co zupenie innego
a trzy wywoania funkcji.
1   (#$ (
SNT (22
  =(>#=(> ,   "  =(>  #; =(> 

Zwr uwag, e kada z takich operacji jest do kosztowna (w sensie czasu wykonania).
Poza tym oprcz wywoa funkcji kada z tych operacji pociga za sob konieczno przydziau pamici na stercie, skopiowania parametru acucha znakw do pamici przydzielonej na stercie, a nastpnie, podczas wywoania destruktora, zwolnienia tej pamici
i jej zwrotu do systemu. Wykonanie tych wszystkich dziaa jest nieuniknione jeden raz
dla operatora przypisania, ktry posuguje si semantyk wartoci w celu utrzymywania pamici
na stercie odrbnie dla swoich dwu operandw. Jednak robi to jeszcze dwa razy? Raz wobec parametru funkcji operatorowej przeciajcej operator przypisania. Drugi raz wobec
wartoci zwracanej z tej funkcji. Wyglda na to, e to zbyt wiele. Aby wyczerpa ten kopotliwy temat do koca dodajmy, e obiekt tworzony przez konstruktor kopiujcy nie jest
uywany przez kod klienta (przypomn, e zwrot obiektu z funkcji zosta wprowadzony
tylko po to, by poprawnie obsugiwa acuchowe operacje przypisania). Zostaje cakowicie
pozostawiony wasnemu losowi, a nastpnie usunity po wywoaniu destruktora.

Pierwszy rodek zapobiegawczy wicej przeciania


S dwa sposoby, by poprawi efektywno dziaania takiego poddanego przecieniu operatora przypisania. Zmiana typu parametru funkcji operatorowej z obiektu klasy
  na
macierz znakow (acuch znakw) pozwala na wyeliminowanie wywoania konstruktora
konwersji.
;  ; ""  #   =>  ! (&  
        ! &  &  & (.
 #  
 #       0E & ( &
 

+R " H+

+HJ+   ) & 


  7   ,

Jeli chcemy, by poddany przecieniu wobec klasy


  operator przypisania obsugiwa i przypisanie obiektowi tablicy znakowej, i przypisanie obiektu klasy
 , musimy
dwukrotnie podda operator przypisania przecieniu oddzielnie dla parametru typu tablica znakowa, oddzielnie dla typu
 . Wydruk wyjciowy programu z listingu 11.7 po
dodaniu do specyfikacji klasy drugiego przecionego operatora pokazano na rysunku 11.19.
W komunikacie diagnostycznym wydrukowanym z wntrza funkcji obsugujcej drugi z operatorw przypisania zostao dodanych kilka spacji, by atwo byo optycznie odrni komunikaty drukowane przez funkcj obsugujc pierwszy operator przypisania (funkcja z parametrem typu
 ) od komunikatu wydrukowanego przez funkcj obsugujc drugi
operator przypisania (z parametrem typu macierz znakowa).

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

611

Rysunek 11.19.
Wydruk wyjciowy programu
z listingu 11.7 po dodaniu
drugiej funkcji przeciajcej
operator przypisania

Drugi rodek zapobiegawczy zwrot wartoci poprzez referencj


Drugim sposobem, by poprawi efektywno dziaania programu, jest wyeliminowanie nadmiarowych wywoa konstruktora kopiujcego. Aby to osign, powinnimy zastpi zwrot
poprzez warto zwrotem poprzez referencj. Oto przykad takiego zastpienia w kodzie
funkcji przeciajcej operator przypisania, ktrej parametrem jest macierz znakowa.
; - ; ""   #    =>   1(0
        ! &  &  & (.
 #  
 #       0E & ( &
 

+ R " H+

+HJ+   )


  7   ,

To samo powinnimy zrobi w pierwszej funkcji przeciajcej operator przypisania z parametrem typu
 . Gdy z funkcji zwracane s referencje (wicej na ten temat napisano
w dyskusji o funkcjach w rozdziale 9.), powinnimy postpowa ostronie i zawsze si upewni, e referencja cigle wskazuje na wany (funkcjonujcy) obiekt, ktry pozostanie przy
yciu, gdy dana funkcja zakoczy swoje dziaanie. W tym przypadku nie ma takiego niebezpieczestwa. Referencja, ktra zostaje zwrcona z funkcji operatorowej, jest referencj
do obiektu stanowicego lewostronny operand operatora przypisania w przestrzeni klienta,
np. 
5 6 w podanym przykadzie ptli programowej. Ten obiekt pozostaje przy yciu po
zakoczeniu dziaania funkcji operatorowej, poniewa zosta zdefiniowany w przestrzeni klienta
(zakresie widocznoci nazw klienta). Bd ostrony przy prbach zwrotu referencji do obiektw
zdefiniowanych w przestrzeni nazw serwera, ktre znikaj po powrocie z wywoania funkcji
serwera. Wiele kompilatorw wydrukuje jedynie komunikat ostrzegawczy (ang. warning) i taki
bd moe ci uj bezkarnie.
Wydruk wyjciowy programu z listingu 11.7, w wersji z zastosowaniem dwch funkcji
przeciajcych operator przypisania zwracajcych referencje do obiektu, pokazano na rysunku 11.20.
To wyglda tak, jakbymy zamczyli ju biedny operator przypisania na mier, ale to nie jest
jego dumny koniec. Niektrzy puryci upieraliby si, e to jeszcze nie wystarczy, poniewa
taka konstrukcja nie chroni autora kodu klienta przed koniecznoci wykonywania czynnoci,
ktre przecie nie s niezbdne, jak np. modyfikowanie zawartoci zwrconego obiektu

612

Cz II n Programowanie obiektowe w C++

Rysunek 11.20.
Wydruk wyjciowy
programu z listingu 11.7
z zastosowaniem dwch
funkcji przeciajcych
operator przypisania,
zwracajcych referencje
do obiektu klasy String

reprezentujcego acuch znakw przed usuniciem tego obiektu. Na przykad nastpujcy


fragment kodu jest konstrukcj legaln w C++ w odniesieniu do operatorw przypisania
w wersji z listingu 11.7.
 & & "   & ' &   5 5
1   (#$ (
SNT (22
  =(> # =(>  1+S  & ' &  5 5+  ,  

Ten kod przypisuje jeden obiekt drugiemu obiektowi, zwraca referencj do obiektu docelowego
i natychmiast wysya komunikat nakazujcy zmodyfikowane tego obiektu. Przypisana warto nie zostaje nigdy uyta. Nie ma to szczeglnego sensu, zatem powinno zosta zasygnalizowane jako bd skadniowy. Jeli chcemy, by kompilator rzeczywicie wygenerowa komunikat o bdzie skadniowym, powinnimy t zwracan referencj zadeklarowa jako
referencj do staej (przy uyciu sowa  
).
  ; - ; ""   #    =>  ! O
        ! 0  &  &  & (.
 #  
 #      5 0 &   & 
 

+ R " H+

+HJ+   )
  7   ,

Autor nie ma przekonania, czy warto si tu upiera, e koniecznie naleaoby to zrobi,


jednak puryci uzyskali tu punkt. Jeli co nie ma sensu, nie powinno by dozwolone, by
nie mogo wystpi jako legalna cz kodu.

Rozwaania praktyczne
jak chcielibymy to zaimplementowa?
Dynamiczne zarzdzanie pamici powinno by obsugiwane w oparciu o wiedz i zrozumienie. Odstpienie na krok od regu w jakimkolwiek kierunku powoduje ryzyko albo spadku
efektywnoci dziaania, albo utraty integralnoci programu.
Wielu programistw uwaa, e zawsze gdy projektujemy klas, ktra dynamicznie zarzdza pamici, musimy wyposay t klas w peny zestaw pomocniczych metod:
n

konstruktor domylny,

konstruktor (konstruktory) konwersji,

konstruktor kopiujcy,

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy


n

przeciony operator przypisania,

destruktor.

613

Autor nie jest przekonany, czy naley w sposb automatyczny przestrzega takich zalece.
W zalenoci od wymaga kodu klienta moemy potrzebowa tylko czci spord tych
funkcji. Jeli wyposaymy klas w funkcje dokonujce przecienia operatorw z nieprawidowymi interfejsami, wyeliminujemy w ten sposb problem integralnoci programu, ale
jednoczenie pogorszymy efektywno dziaania kodu programu bez adnych racjonalnych
powodw. Autor jest natomiast pewien, e musimy rozumie zagadnienia omwione w niniejszym rozdziale. To zrozumienie pozwoli nam na dobr metod zgodnie z oczekiwanymi
zadaniami do wykonania (tj. zgodnie z wymaganiami kodu klienta) oraz na zaprojektowanie klasy, ktra jednoczenie jest i efektywna, i poprawna. Jeli automatycznie wyposaamy klas w ca t maszyneri, kod klienta wykonuje si poprawnie, ale tracimy ostro widzenia zagadnie i zapominamy o rnicy pomidzy inicjowaniem a przypisaniem. To jest
wrcz niebezpieczne.
Naley zawsze si upewni, e w odniesieniu do klas stosuje si waciwe narzdzia. Jeli
pojawi si problemy, naley przeanalizowa sytuacj, zastosowa komunikaty uatwiajce
ledzenie dziaania programu, narysowa schematyczne diagramy, ale nie przecia klas
komponentami, ktre nie s im niezbdne. Upewnij si, e dobrae odpowiednie narzdzia
do pracy wymagajcej wykonania. Nie kr wok bezproduktywnie z motkiem i gwodziami w rkach (konstruktory, funkcje operatorowe przypisania i inne narzdzia), szukajc oparcia
dopiero na przeciwlegej cianie. Pamitaj, e konstruktor kopiujcy i operator przypisania
su do rozwizywania rnych zada i nie mog by stosowane zamiennie. W przenoni
mona o nich powiedzie, e su jakby do zawieszania obrazkw na przeciwlegych sobie
cianach.
Czsto kod klienta nie potrzebuje moliwoci zainicjowania jednego obiektu przy uyciu
innego obiektu ani przypisania jednego obiektu drugiemu obiektowi. Zamy, e klasa, ktr
mamy zaimplementowa, reprezentuje okno. Dla uproszczenia rozpatrzmy tylko jedno pole
danych, reprezentujce tekst, ktry ma by wywietlany w tym oknie. Taka klasa, powiedzmy
7 , jest podobna do klasy
 . Zawiera tablic znakow umieszczan na stercie
w dynamicznie przydzielanej pamici, destruktor i funkcj dokonujc przecienia operatora konkatenacji, akceptujc jako argument tablic znakow, ktra ma zosta wywietlona w oknie i dodaje ten tekst do zawartoci okna.
 W 

 7     &  ! & 
 
!"
W 
  # $  #   =$># $  ,    54
/W 
    ,   0   0
    2#    =>  ! (&  
  #   2  
7  #  = 2 %>    (..  )E ( 
1 ##?@AA 3 % 
        '   &5&'
    #  ,     &<&    
  7    
    , ,    &<&   )

614

Cz II n Programowanie obiektowe w C++


Aby mie penosprawne okno, potrzebowalibymy wicej pl danych i metod nalecych
do tej klasy, ale taki projekt wystarcza do zaprezentowania zwizanych z tym zagadnie.
Oczywicie, w aplikacji jest nieco mniej obiektw klasy 7  ni obiektw klasy
 .
Poza tym gdy tworzony jest obiekt klasy 7 , zostaje on zainicjowany tak, by jego zawarto bya pustym acuchem znakw (puste okno), a dane s dodawane w miar wykonania kodu programu.
Jak pokazano na pocztku niniejszego rozdziau, to dokadnie ten rodzaj klas, ktrych obiekty
nie mog by przekazywane poprzez warto. Co si stanie, gdy programista piszcy kod
klienta przekae obiekt klasy 7  jako parametr poprzez warto albo po prostu zapomni
o wpisaniu operatora $ i w ten sposb utworzy przekazywanie poprzez warto niechccy?
     W      '! &6
  

     ,

Nie ma sensu inicjowanie jednego okna przy uyciu innego ani przypisywanie jednemu obiektowi typu okno innego obiektu tego typu.
 W  B W    D 6
W  % % 2# +W  Q V 6+    .
W  : # %    .
: # %  (  !(   .
 :   &   )E B   

Nie. Drugi i trzeci wiersz w podanym fragmencie kodu zdecydowanie nie maj sensu. Znakomita wikszo ludzi nie napisaaby czego takiego. Co wicej, argument do funkcji
  !zostaje przekazany poprzez warto. Wikszo ludzi (szczeglnie spord tych,
ktrzy czytaj t ksik) nie napisaaby tak. Skoro wikszo ludzi nie pisaaby programu
w taki sposb, czy oznacza to, e klasa 7  mogaby zosta zbudowana bez konstruktora
kopiujcego albo bez przecienia operatora przypisania? Jeli kto (kto najwyraniej nie
czyta tej ksiki) napisa kod podobny do podanego fragmentu, to byby w stanie spowodowa
jednoczenie problemy z zachowaniem i integralnoci, i efektywnoci dziaania programu.
Cho przecie ten kod jest formalnie legalny w C++.
Czy do naszej klasy 7  powinnimy doda obszerne komentarze? Szanowny Programisto,
piszcy kod klienta, nie inicjuj, prosz, obiektw klasy 7  przy uyciu innych obiektw
klasy 7 . No i, bardzo, bardzo prosz, nie przekazuj obiektw klasy 7  poprzez
warto jako parametrw do funkcji ani nie zwracaj tych obiektw poprzez warto z funkcji.
Inaczej Twj program bdzie mia kopoty. Mio byoby zrobi co wicej w celu ochrony
kodu klienta.
Jednym ze sposobw jest dodanie do klasy konstruktora kopiujcego i funkcji operatorowej
przeciajcej operator przypisania. Jeli teraz programista tworzcy kod klienta napisze niewaciwy kod, przynajmniej nie bdzie on powodowa problemw z zachowaniem integralnoci programu.
Innym sposobem jest doprowadzenie do takiej sytuacji, by niepodany kod by niedopuszczalny ze skadniowego punktu widzenia. To bardzo ciekawa koncepcja.

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

615

Istota sprawy polega na tym, by zaprojektowa nasz klas w taki sposb, e prba niewaciwego uycia obiektw tej klasy w kodzie klienta bdzie wychwytywana przez kompilator jako bd skadniowy. To projektant klasy decyduje, jakie zastosowanie jest nieprawidowe. Potem ju nie potrzebujemy adnego komentarza w stylu Szanowny Programisto,
piszcy kod klienta.
Tyle tylko, e to nie jest takie atwe. Moemy usun konstruktor kopiujcy i funkcj przeciajc operator przypisania zdefiniowane przez programist, ale kompilator automatycznie doda do naszej klasy domylne wersje konstruktora kopiujcego i operatora przypisania.
A to s wanie te funkcje, metody dodawane automatycznie przez system, ktre powoduj
powstawanie problemw z zachowaniem integralnoci w przypadku klas z dynamicznym
zarzdzaniem pamici. Aby temu zapobiec, dodajmy do klasy konstruktor kopiujcy i funkcj dokonujc przecienia operatora przypisania zdefiniowane przez programist. Trzeba
to jednake zrobi w taki sposb, by kod klienta nie mg ich zastosowa i by kada prba
ich wywoania powodowaa wystpienie bdu skadniowego.
Czy ju wida, dokd zmierza i prowadzi nas autor? Autor namawia oto czytelnika do napisania funkcji, ktrej kod klienta nie moe wywoa. Jak mona napisa tak funkcj, by kod
klienta nie mg jej wywoywa? Jednym z moliwych rozwiza jest zadeklarowanie tej funkcji jako metody niepublicznej poprzez umieszczenie jej w sekcji prywatnej (lub chronionej).
Na listingu 11.8 przedstawiono takie wanie rozwizanie. Konstruktor kopiujcy i metoda
dokonujca przecienia operatora przypisania zostay zadeklarowane jako prywatne. Po takiej
deklaracji nie musz nawet zosta zaimplementowane. Jeli podany jest tylko prototyp funkcji,
a funkcja zostaje wywoana przez kod klienta, jest to bd konsolidacji (ang. linker error).
W tym przypadku konsolidator nie znajdzie kodu funkcji. Kompilator zaprotestuje, e trzy
ostatnie wiersze kodu w obrbie funkcji   s bdne. Jeli deklaracje tej metody dokonujcej przecienia operatora i konstruktora kopiujcego poprzedzimy znakiem komentarza (w ten sposb wyczajc je z kodu), kompilator zaakceptuje ten sam kod klienta, aprobujc
w ten sposb formalnie i przygotowujc do wykonania ten naprawd nierozsdny kod.
Listing 11.8. Przykad prywatnych prototypw w celu wykluczenia nieprawidowego posugiwania si
obiektami po stronie klienta

 
    
 W 

 7      ! & 
 
W   W -      &  &  & (.
W -   #   W  -        
!"
W 
  # $  #   =$># $  ,    54
/W 
    ,   0   0
    2#    =>  ! (&  
  #   2  
7  #  = 2 %>    (..  )E ( 
1 ##?@AA 3 % 
        '   &5&'

616

Cz II n Programowanie obiektowe w C++


    #  ,     &<&    
  7    
    , ,     &<&  
  &( !& '    )E
     W   
  

     ,
 

W  % % 2# +W  Q V 6J+    .
W  : # %    . B !5. &5
: # %  (  !(   . B !5. &5
 :   &    )E B !5. &5
  $
,

To doskonaa metoda, by zapobiec niewaciwemu stosowaniu naszych klas przez programist piszcego kod klienta. Oczywicie jeli taki kod, ktry w obrbie funkcji   na
listingu 11.8 zosta opatrzony delikatnym komentarzem nierozsdne, musi by poprawnie
obsugiwany (z jakichkolwiek powodw), a nie ma ogranicze ze strony efektywnoci dziaania, klasa musi zawiera konstruktor kopiujcy i funkcj przeciajc operator przypisania
lub kilka wersji operatorw przypisania, jeli moliwe jest stosowanie wielu typw wyrae
wystpujcych po prawej stronie operatora przypisania. Dopki rozwaany jest taki operator
konwersji (operatory konwersji), powinnimy doczy do klasy stosowne funkcje, skoro
obiekty danej klasy maj by inicjowane za pomoc prostych zmiennych zawierajcych dane,
a nie za pomoc obiektw tego samego typu. Innym uzasadnieniem, by doda do klasy operatory konwersji, jest ch uniknicia koniecznoci stosowania wielokrotnych funkcji dokonujcych przecienia operatorw. W ten sposb zmniejszamy ilo funkcji zawartych
w obrbie klasy, ale w zamian mamy dodatkowe wywoania konstruktora i operacje przydziau dynamicznej pamici.

Podsumowanie
W niniejszym rozdziale przygldalimy si ciemnym stronom potgi C++. Autor nie zamierza przestraszy czytelnika, lecz raczej uwiadomi mu powag sytuacji i skal odpowiedzialnoci programisty piszcego w C++, od ktrego zaley i efektywno dziaania programu, i zachowanie jego integralnoci.
Autor wytoczy kolejne wakie argumenty przeciwko przekazywaniu parametrw poprzez
warto i ma nadziej, e w naszych programach nie bdziemy skonni pj na aden kompromis. Przekazujmy parametry poprzez referencje i stosujmy modyfikator  
do wskazania,
e okrelony parametr nie zostanie zmodyfikowany podczas wykonania danej funkcji.
Autor przytacza take argumenty przeciwko zwrotowi obiektw z funkcji poprzez warto.
Jeli musimy zwrci z funkcji obiekt, zwrmy referencje do tego obiektu, upewniwszy
si jednak, e jest to referencja do takiego obiektu, ktry nie zniknie natychmiast po powrocie
z danego wywoania funkcji.

Rozdzia 11. n Konstruktory i destruktory potencjalne problemy

617

Jeli jestemy zdecydowanie przekonani, e kod klienta nie powinien przekazywa obiektw naszej klasy poprzez warto, zadeklarujmy konstruktor kopiujcy jako prywatny poprzez umieszczenie jego prototypu w prywatnej sekcji specyfikacji klasy. W takiej sytuacji
nie ma potrzeby implementowania takiego konstruktora.
Jeli nasza klasa dynamicznie zarzdza pamici, upewnijmy si, e wyposaylimy t klas w destruktor, ktry zwalnia i zwraca do systemu pami na stercie.
Jeli obiekty naszej klasy maj by po stronie kodu klienta wykorzystywane do inicjowania
jednego obiektu przy uyciu innego obiektu tej samej klasy, dodajmy do specyfikacji klasy
konstruktor kopiujcy, ktrego implementacja stosuje semantyk wartoci wobec naszej
klasy i wyposaa kady obiekt w jego wasny, odrbny segment pamici na stercie. W kodzie przeciajcym operator przypisania upewnijmy si, e zapobieglimy wyciekom pamici
poprzez zwrot do systemu tego segmentu pamici na stercie, ktrym dysponowa docelowy
obiekt przed przypisaniem mu nowej wartoci skopiowanej ze rdowego obiektu-parametru.
Upewnijmy si, e ta pami nie zostaje zwrcona przed sprawdzeniem, czy nie mamy tu
do czynienia z samokopiowaniem (czyli przypisaniem obiektowi jego wasnej wartoci). Zdecydujmy, czy chcemy obsugiwa operacje acuchowe. Czsto nasi klienci nie bd mie
takich potrzeb.
Zastosowanie konstruktorw konwersji pozwala na znaczce rozlunienie rygorystycznych
regu kontroli zgodnoci typw w C++. Jako rzeczywisty, biecy argument moemy przekazywa dane innego typu ni typ wymagany przez dan klas, a mimo to kod bdzie
uznawany za formalnie poprawny. To wspaniae, ale stosujmy takie techniki ostronie. Dodatkowe wywoania konstruktorw konwersji s kosztowne (w sensie czasu wykonania), szczeglnie wtedy, gdy musimy stosowa semantyk wartoci.
Oczywicie jeszcze jedno. Upewnijmy si, e rozrniamy, w ktrym miejscu kod klienta
wywouje konstruktor kopiujcy, a w ktrym funkcj operatorow przeciajc operator przypisania. W obu przypadkach operacja po stronie klienta jest oznaczana tym samym
symbolem rwnoci, ale powoduje to wywoanie rnych funkcji po stronie kodu serwera.
Powinnimy wiedzie, ktra z nich jest stosowana w ktrym miejscu.
Autor radzi czsto wraca do materiau, ktry obejmuje niniejszy rozdzia. Rysujmy diagramy
wykorzystania pamici, eksperymentujmy z przykadowymi kodami. Pamitajmy zawsze, e
dynamiczne zarzdzanie pamici w C++ moe atwo przerodzi si w co w rodzaju sonia
w skadzie porcelany albo wycieczki czogiem po ssiedzkich ogrdkach. To le, e do tradycyjnych kategorii bdw programistycznych, bdw skadniowych i bdw semantycznych (w ruchu, ang. run-time errors) C++ dodaje jeszcze jedn kategori bdw. Program
moe by formalnie poprawny skadniowo i rwnoczenie poprawny semantycznie, a mimo
to nadal by nieprawidowy. aden inny jzyk programowania nie obcia programisty
tak ogromn odpowiedzialnoci. Miejmy zawsze pewno, e przyjmujemy t odpowiedzialno z nalenym respektem.
Powodzenia.

You might also like