Ludzie pragną czasami się rozstawać, żeby móc tęsknić, czekać i cieszyć się z powrotem.
W tym celu musimy właśnie użyć wielometod.
330
C++. Styl i technika zaawansowanego programowania
Przyjrzymy się im najpierw na prostym przykładzie klasy dla operatora doda-
wania. Implementując wielometodę, 2 wykorzystamy przemienność operacji
dodawania zmniejszając w ten sposób o połowę liczbę przypadków, które należy roz-
patrzyć. Do rozpoznawania typu argumentów użyjemy funkcji wirtualnej (( .
Chociaż jest to funkcja wirtualna, to jej zastosowanie odpowiada rozwiązaniu polegającemu na zastosowaniu pola informującego o typie obiektu przyjmującego wartości
typu wyliczeniowego.
Pierwszą czynnością będzie dodanie nowej funkcji składowej do klasy i jej klas
pochodnych: ,*0 0 !+. Funkcja ta będzie zwracać wartość logiczną
, jeśli wywołana zostanie dla obiektu klasy , z parametrem wskazującym obiekt
klasy i zachodzi związek IS-A ,. Zadaniem tej funkcji jest więc ustalenie, który
z argumentów operatora dodawania jest bardziej ogólny i powinien przejąć kontrolę
nad wykonaniem operacji.
Operatory konwersji występujące we wcześniejszych przykładach zastąpimy pojedynczą
funkcją składową . Funkcja ta wykonywać będzie konwersje dowolnego obiektu
typu do typu obiektu, dla którego została wywołana. Przy założeniu, że funk-
cja będzie wywoływana dla wszystkich niezbędnych konwersji, nie jest już konieczne przeciążanie operacji dodawania dla każdej klasy kopertowej. Każda klasa
posiadać będzie jedną wersję operacji dodawania. (Nazwa operacji została zmieniona
z 2 na , aby uniknąć niejednoznaczności związanej z wprowadzeniem
operatora 2 przedstawionego poniżej). Ponieważ każda koperta może przyjąć, że jej operacja wywoływana jest zawsze z parametrem tej samej klasy, to funkcja
nie musi już korzystać z instrukcji wyboru :
M;
4444
M;
/M;
M; ;M;<
M;M;<
M; .M;<*M;<
4444
'; 7 M;
4444
/M;
,9 ::; 7#7;
M; ;M;<
zawsze zwraca obiekt typu Complex
4 :: ;! #7;
M;+*4;!
4 :: !#7;
M;4;! *+
4444
4444
poniższa operacja wykonywana jest jedynie dla obiektów typu Complex
Rozdział 9. ♦ Emulacja języków symbolicznych w C++
331
M;M;<
X;! '; 7
4444
/M;
,9 :: ;! #7; __
'; 7 /
brak operacji promote — żadne obiekty nie wymagają promowania
do klasy Imaginary
poniższa operacja wykonywana jest jedynie dla obiektów typu Imaginary
M;M;<
Efekt odpowiadający wielometodom uzyskujemy, wprowadzając globalnie przeciążony
2, który kompilator stosuje dla dowolnych argumentów klasy :
M; .M;<)*M;
)4 /
L4;
L4 /<)
M;; :)4 ;L
)4;
M; #
Zauważmy, że rozwiązanie takie posiada szereg zalet w stosunku do podejścia przed-
stawionego w podrozdziale 5.5 na stronie 140. Funkcje składowe są bardziej spójne
— promocje do danego typu odbywają się lokalnie w danej klasie, a wszystkie ope-
ratory binarne wykonywane są dla argumentów o homogenicznym typie (w związku
z tym nie musimy przeciążać operatorów). Definicje funkcji składowych nie zawierają
już instrukcji wyboru .
Przedstawione rozwiązanie przybliża sposób działania języków symbolicznych. W nie-
których implementacjach języka Smalltalk klasy numeryczne bezpośrednio obsługują
operacje arytmetyczne, jeśli oba argumenty są tego samego typu. Jeśli typy nie są zgodne,
to wysyłany jest komunikat do klasy, aby obsłużyła wszelkie niezbędne promocje i kon-
wersje, a następnie wywołała metodę odpowiedniej klasy pochodnej.
Rozwiązanie w języku Smalltalk odpowiada więc sytuacji, w której nasz globalnie
przeciążony operator postaci:
.M;<*M;<
byłby zadeklarowany jako:
M; .M;<
332
C++. Styl i technika zaawansowanego programowania
Niektóre środowiska programowania w języku Lisp implementują wielometody jako
kaskady wywołań funkcji wirtualnych dynamicznie wyszukujące ścieżkę do metody
określonej przez typy wielu argumentów.
Ćwiczenia
1. Zmodyfikuj funkcję (( (oraz inne fragmenty kodu, jeśli to konieczne)
tak, by możliwe było dodawanie nowych funkcji wirtualnych do klasy.
Pamiętaj, że oryginalna tablica dla danej klasy jest osadzona statycznie
w pamięci. Załóż, że kompilator umieszcza elementy tablicy odpowiadające
nowym funkcjom wirtualnym na końcu tablicy . Zastanów się, jakie
ograniczenia posiada takie rozwiązanie w przypadku dziedziczenia po klasie
listu oraz w jaki sposób można dodać funkcje wirtualne w klasach pochodnych.
2. Dodaj funkcję składową (('* !+, która umożliwi powrót
struktury klasy i funkcji wirtualnych do wersji poprzedzającej ostatnią
aktualizację.
3. Zmodyfikuj program zamieszczony w dodatku E tak, by odzyskiwanie
nieużytków wykonywane było za każdym razem, gdy tworzony jest nowy
obiekt.