2010.07_Wielometody Rozszerzenie funkcji wirtualnych_[Programowanie C C++].pdf
(
1036 KB
)
Pobierz
441723129 UNPDF
PROGRAMOWANIE C++
Wielometody
Rozszerzenie funkcji wirtualnych
Mechanizm funkcji wirtualnych pomaga wybrać metodę,
biorąc pod uwagę rzeczywisty typu obiektu, dla którego
daną metodę się woła. Jeżeli istnieje potrzeba wyboru
funkcji w zależności od dwóch lub większej ilości typów, to
odpowiedni mechanizm musimy dostarczyć sami.
Dowiesz się:
• Co to są wielometody;
• Jak implementować ten mechanizm w C++.
Powinieneś wiedzieć:
• Jak pisać proste programy w C++;
• Co to jest dziedziczenie i funkcje wirtualne;
• Co to jest wzorzec wizytatora;
• Co to jest programowanie generyczne.
alne) pozwala współdzielić kod: posługuje-
my się interfejsem do klasy bazowej, nato-
miast specyficzne dla danego typu operacje tworzymy
jako nadpisane funkcje wirtualne. Takie podejście jest
bardzo wygodne, dlatego jest dostarczane przez języ-
ki wspierające obiektowe podejście do programowania
(np. przez C++). Funkcje wirtualne pozwalają na wy-
bór odpowiedniej wersji nadpisanej metody, uwzględ-
niając rzeczywisty typ obiektu, dla którego tę meto-
dę wołamy. Mechanizm ten ma ograniczenia – wybie-
ramy funkcję składową w zależności od jednego ty-
pu Jeżeli istnieje potrzeba wyboru funkcji w zależno-
ści od dwóch lub większej ilości typów, to odpowied-
ni mechanizm musimy dostarczyć sami, jeżeli progra-
mujemy w C++. Mechanizm ten nazywany jest wielo-
metodą (ang.
multimethods,
multiple dispatch
). Pew-
ne obiektowe języki programowania dostarczają wie-
lometod, na przykład Common Lisp ma wbudowane
wielometody, Python – mechanizm ten dostępny jako
rozszerzenie. Poniższy artykuł pokazuje implementa-
cje wielometod w C++.
Przykład, którym będziemy się posługiwali, aby po-
kazać zastosowania wielometod, dotyczy hierarchii fi-
gur, pokazanej na Rysunku 1. Na początek przyjrzyj-
my się możliwościom, które dają funkcje wirtualne. Dla
figury możemy dostarczyć metodę obliczającą jej pole.
Aby wybierać odpowiedni algorytm obliczania pola, sto-
sujemy mechanizm późnego wiązania, ponieważ funk-
cja ta jest zależna od jednego typu (typu figury, dla któ-
rej obliczamy pole). Implementacja jest typowa: dostar-
czamy funkcję wirtualną w klasie bazowej, nadpisujemy
ją w klasach konkretnych, dostarczając odpowiedniego
algorytmu obliczania pola. Za wywołanie odpowiedniej
metody jest odpowiedzialny mechanizm funkcji wirtual-
nych dostarczany przez język, patrz Listing 1.
Mechanizm ten nie wystarczy do implementacji funk-
cji
intersect
, która zwraca pole przecięcia dwóch figur.
Jeżeli dysponujemy odpowiednimi funkcjami zawierają-
cymi algorytmy obliczania pola przecięcia kół, prostoką-
tów, prostokąta i koła itp., to problem sprowadza się do
odpowiedniego wyboru tych funkcji, w zależności od rze-
czywistych typów dwóch obiektów. Przykładowo, jeżeli
dysponujemy funkcją
double intersect(const Circle&
a, const Circle& b)
, która dostarcza pola przecięcia
dwóch kół, funkcją
double intersect(const Circle& a,
const Rect& b)
oraz funkcją
double intersect(const
Rect& a, const Rect& b)
, to należy wywołać odpowied-
nią, mając dwa uchwyty do typu
Figure&
(patrz Rysu-
nek 2). Wielometody umożliwiają takie wołanie, podob-
Szybki start
Aby uruchomić przedstawione przykłady, należy mieć do-
stęp do kompilatora C++ oraz edytora tekstu. Na wydru-
kach pominięto dołączanie odpowiednich nagłówków oraz
udostępnianie przestrzeni nazw, pełne źródła dołączono ja-
ko materiały pomocnicze.
20
7/2010
M
echanizm późnego wiązania (funkcje wirtu-
Wielometody
nie jak funkcje wirtualne, gdy wybór dotyczy jednego ty-
pu.Wielometody umożliwiają takie wołanie.
cji
double intersect(const Figure&, const Figure&)
.
Funkcja ta otrzymuje dwa argumenty, ich typy nie są
znane, mamy uchwyty do klasy bazowej. Funkcja
intersect
będzie wielometodą, będzie wołać odpo-
wiednią wersję przeciążonej funkcji, pokazanej na Li-
stingu 2, określając konkretne typy argumentów.
Wielometody wykorzystujące
rzutowanie dynamiczne
Celem naszego działania będzie dostarczenie funk-
Listing 1.
Klasy reprezentujące �gury. Późne wiązanie pozwala tworzyć metody zależne od jednego typu (typu �gury)
class
Figure
{
//klasa bazowa
public
:
virtual
double
getArea
()
const
=
0
;
//funkcja wirtualna, musi być nadpisana w klasie konkretnej
virtual
~
Figure
()
{}
}
;
class
Rect
:
public
Figure
{
//klasa konkretna
public
:
virtual
double
getArea
()
const
{
//nadpisuje metodę
//oblicza pole prostokąta
}
//pozostałe metody i składowe
}
;
class
Circle
:
public
Figure
{
//inna klasa konkretna
public
:
virtual
double
getArea
()
const
{
//nadpisuje metodę
//oblicza pole koła
}
}
;
Figure
*
f
=
new
Rect
();
//posługujemy się wskaźnikiem do klasy bazowej
double
area
=
f
->
getArea
();
//
wo
ł
a
metod
ę
uwzgl
ę
dniaj
ą
c
rzeczywisty
typ
obiektu
(
tutaj
typem
tym
jest
Rect
)
Listing 2.
Funkcje obliczające odpowiednie pola przecięcia. Argumentami są typy konkretne
double
intersect
(
const
Circle
&
a
,
const
Circle
&
b
)
;//oblicza pole przecięcia dwóch kół
double
intersect
(
const
Circle
&
a
,
const
Rect
&
b
);
//oblicza pole przecięcia prostokąta i koła
double
intersect
(
const
Rect
&
a
,
const
Rect
&
b
);
//
oblicza
pole
przeci
ę
cia
dw
ó
ch
prostok
ą
t
ó
w
Listing 3.
Wielometody wykorzystujące rzutowanie dynamiczne. Implementacja bardzo niewydajna i trudna w pielęgnacji
double
intersect
(
const
Figure
&
a
,
const
Figure
&
b
)
{//dostarczamy typów bazowych
if
(
Circle
*
pa
=
dynamic_cast
<
Circle
*>(&
a
))
{//bada typ pierwszego argumentu
if
(
Circle
*
pb
=
dynamic_cast
<
Circle
*>(&
b
))
{
return
intersect
(*
pa
,
*
pb
)
;//ma konkretne typy, woła dla dwóch kół
}
else
if
(
Rect
*
pb
=
dynamic_cast
<
Rect
*>(&
b
)
{
return
intersect
(*
pa
,
*
pb
)
;//woła przecięcie dla koła i prostokąta
}
}
else
if
(
Rect
*
pa
=
dynamic_cast
<
Rect
*>(&
a
))
{//bada typ pierwszego argumentu
if
(
Circle
*
pb
=
dynamic_cast
<
Circle
*>(&
b
))
{
return
intersect
(*
pb
,
*
pa
)
;//zamienia kolejność argumentów, aby wołać odp. funkcję
}
else
if
(
Rect
*
pb
=
dynamic_cast
<
Rect
*>(&
b
)
{
return
intersect
(*
pa
,
*
pb
)
;//woła przecięcie dla dwóch prostokątów
}
}
return
0.0
;//nieznane typy
}
www.sdjournal.org
21
PROGRAMOWANIE C++
Wielometodę możemy realizować bezpośrednio, wy-
korzystując rzutowanie dynamiczne. Przykład imple-
mentacji pokazano na Listingu 3. Jest ona bardzo niewy-
dajna i trudna w pielęgnacji, ponieważ posługuje się łań-
cuchem operacji rzutowania typu
dynamic_cast
. W ciągu
instrukcji warunkowych badamy rzeczywisty typ pierw-
szego argumentu, a następnie rzeczywisty typ drugiego
argumentu. Wykonujemy przy okazji wiele operacji rzu-
towania dynamicznego, a każda z nich jest uznawana
za kosztowną, ponieważ przegląda listę klas w hierarchii
(a jeżeli stosujemy dziedziczenie wielobazowe, to prze-
gląda drzewo). Kod jest rozwlekły i zawiera powtórze-
nia (taki sam kod dla badania typu drugiego argumen-
tu, gdy typ pierwszego jest ustalony). Trudność w pielę-
gnacji wynika także z konieczności modyfikacji łańcucha
instrukcji warunkowych po zmianie hierarchii klas, gdyż
w łańcuchu tym badamy wszystkie typy. Dla dużej liczby
typów konkretnych kod znacznie się rozrasta, ponieważ
musimy uwzględnić wszystkie pary typów. Takie rozwią-
zanie ma jedną zaletę – jest bardzo proste. Opisane po-
niżej sposoby tworzenia wielometod będą starały się
usunąć wady przedstawionego rozwiązania.
Rysunek 1.
Klasy reprezentujące �gury, wykorzystane do
demonstracji działania wielometod
Wykorzystanie wizytatora
Wybór funkcji w zależności od wielu typów można
oprzeć o wzorzec wizytatora. Wzorzec ten oraz jego
realizacja w C++ był tematem artykułu w SDJ 4/2010.
Rysunek 2.
Funkcje obliczające pole przecięcia �gur gdy znany jest
ich konkretny typ
Listing 4.
Wizytator dla hierarchii �gur
class
Visitor
{//wizytator bazowy
public
:
virtual
void
visit
(
const
Rect
&)
=
0
;
virtual
void
visit
(
const
Circle
&)
=
0
;
}
;
class
Figure
{//klasa bazowa dostarcza metody accept
public
:
virtual
void
accept
(
Visitor
&
v
)
const
=
0
;
//pozostałe metody
}
class
Rect
:
public
Figure
{//klasa konkretna
public
:
virtual
void
accept
(
Visitor
&
v
)
const
{
return
v
.
visit
(*
this
)
;//woła metodę visit(const Rect&) dla wizytatora
}
//pozostałe metody i składowe
}
;
class
Circle
:
public
Figure
{//klasa konkretna
public
:
virtual
void
accept
(
Visitor
&
v
)
const
{
return
v
.
visit
(*
this
)
;//woła metodę visit(const Circle&) dla wizytatora
}
//pozostałe metody i składowe
}
;
22
7/2010
Wielometody
Wizytator pozwala na wybór typu bez rzutowania dy-
namicznego. Klasy, dla których chcemy stosować to
rozwiązanie muszą zostać zmodyfikowane. Powinny
one dostarczać metodę
accept
. Należy także utworzyć
klasę wizytatora bazowego, patrz Listing 4.
Tworząc wielometodę, będziemy stosowali wizytator
wielokrotnie. Jeżeli rozważamy wybór funkcji w zależ-
ności od dwóch typów (najprostsza wersja wielometo-
dy), to wizytator stosujemy dwa razy, do wyboru pierw-
szego i drugiego argumentu. Wizytator nie wykorzy-
stuje rzutowania dynamicznego, dlatego sposób ten
jest bardzo wydajny. Wadą rozwiązania jest tworze-
nie klas pomocniczych (wizytatorów) oraz niebanal-
ne przepływy sterowania. Przykład dla przecięcia figur
pokazano na Listingu 5.
Wielometoda używa klasy pomocniczej,wizytato-
ra
IntersectVisitor,
który pozwala rozstrzygnąć, ja-
ki jest typ jednego (pierwszego) obiektu. Na wydru-
ku pokazano, że wizytator jest przekazany jako ar-
gument metody
accept
dla obiektu
a,
więc w odpo-
wiedniej metodzie
visit
dostaniemy obiekt
a
przeka-
zany jako typ konkretny. Mamy więc rzeczywisty typ
obiektu
a
.
Aby wyznaczyć rzeczywisty typ obiektu
b
, stosujemy
wizytator po raz drugi. W zależności od typu pierwsze-
go obiektu wizytatorem tym będzie
CircleVisitor
, je-
żeli obiekt
a
jest kołem, albo
RectVisitor
, jeżeli obiekt
a
jest prostokątem. Różne klasy wizytatorów pomocni-
czych użytych do wyznaczania typu drugiego obiektu
są potrzebne, ponieważ przechowują one rzeczywisty
typ pierwszego argumentu.
Metody
visit
dla wizytatora wołanego na rzecz
obiektu
b
dostarczają typu tego obiektu. Wewnątrz
nich możemy wołać odpowiednią funkcję
intersect
,
Listing 5.
Wykorzystanie wizytatora do wyboru funkcji w zależności od dwu typów
struct
CircleVisitor
:
public
Visitor
{
//wizytator, gdy pierwszym typem jest koło
CircleVisitor
(
const
Circle
&
c
)
:
c_
(
c
)
,
value_
(
0.0
)
{}
//przekazuje jeden argument
virtual
void
visit
(
const
Circle
&
c
)
{
value_
=
intersect
(
c_
,
c
);
}//przecięcie koła z kołem
virtual
void
visit
(
const
Rect
&
r
)
{
value_
=
intersect
(
c_
,
r
);
}//przecięcie koła z prostokątem
const
Circle
&
c_
;
//pierwszy obiekt przechowywany jako typ konkretny
double
value_
;//wynik
}
;
struct
RectVisitor
:
public
Visitor
{//wizytator, gdy pierwszym typem jest prostokąt
RectangleVisitor
(
const
Rect
&
r
)
:
r_
(
r
)
,
value_
(
0.0
)
{}
virtual
void
visit
(
Rect
&
r
)
{
value_
=
intersect
(
r
,
r_
);
}
//przecięcie prostokąta z prostokątem
virtual
void
visit
(
Circle
&
c
)
{
value_
=
intersect
(
c
,
r_
);
}
//przecięcie koła z prostokątem
Rect
&
r_
;
//pierwszy obiekt przechowywany jako typ konkretny
double
value_
;//wynik
}
;
struct
IntersectVisitor
:
public
Visitor
{
//rozstrzyga dwa typy, wykorzystuje wizytatory pomocnicze
IntersectVisitor
(
const
Figure
&
ig
)
:
ig_
(
ig
)
,
value_
(
0.0
)
{
}//przechowuje drugi obiekt
virtual
void
visit
(
const
Circle
&
c
)
{//pierwszym typem jest koło
CircleVisitor
circVisitor
(
c
)
;//tworzy odpowiedni wizytator pomocniczych
ig_
.
accept
(
circVisitor
);
//wybiera metodę w zależności od typu drugiego argumentu
value_
=
circVisitor
.
value_
;
//przekazuje wynik obliczeń
}
virtual
void
visit
(
const
Rect
&
r
)
{
//pierwszy typ to prostokąt
RectVisitor
rectVisitor
(
r
);
//tworzy odpowiedni wizytator
ig_
.
accept
(
rectVisitor
);
value_
=
rectVisitor
.
value_
;
}
const
Figure
&
ig_
;//przechowuje jeden z obiektów
double
value_
;
warto
ść
zwracana
}
;
double
intersect
(
const
Figure
&
a
,
const
Figure
&
b
)
{
/
multimetoda
wykorzystuj
ą
ca
wizytator
IntersectVisitor
visitor
(
b
);
a
.
accept
(
visitor
);
return
visitor
.
value_
;
}
www.sdjournal.org
23
PROGRAMOWANIE C++
ponieważ mamy konkretne typy obu argumentów. Wy-
niki obliczeń są przechowywane w składowej
value_
wizytatora, a następnie zwracane użytkownikowi.
Przedstawione rozwiązanie jest wydajne, ale wymaga
ono żmudnego tworzenia klas pomocniczych (wizytato-
rów). Dla wielometody pozwalającej na wybór w zależ-
ności od dwóch typów, gdy różnych typów jest N, nale-
ży stworzyć N+1 wizytatorów. Tworzenie tych klas moż-
na automatyzować, wykorzystując metaprogramowanie
i bibliotekę
boost::mpl
(
patrz SDJ 12/2009
). Przykład
szablonów dla wielometod zawiera biblioteka
faif
(patrz
ramka), ich opis zostanie zawarty w książce
Średnioza-
awansowane programowanie w C++
.
Bezpośrednia implementacja
późnego wiązania
Późne wiązanie możemy zaimplementować bezpo-
średnio, wykorzystując wielowymiarową tablicę wskaź-
ników. Prosty przykład pokazano na Listingu 6.
Implementacja bezpośrednia wymaga dostarczenia
szeregu funkcji o tym samym interfejsie, więc musimy
posłużyć się operatorami rzutowania dynamicznego.
Więcej w książce
Zagadnienia dotyczące współcześnie stosowanych technik w języku C++, wzorce projektowe, programowanie generyczne, pra-
widłowe zarządzanie zasobami przy stosowaniu wyjątków, programowanie wielowątkowe, ilustrowane przykładami stosowany-
mi w bibliotece standardowej i bibliotekach boost, zostały opisane w książce ,,Średniozaawansowane programowanie w C++'',
która ukaże się niebawem.
Listing 6.
Wykorzystanie tablicy wskaźników do funkcji do implementacji wielometod
//funkcja pomocnicza, dostarcza odpowiedni interfejs
double
intersectCircleCircle
(
const
Figure
&
a
,
const
Figure
&
b
)
{
return
intersect
(
dynamic_cast
<
const
Circle
&>(
a
)
,
dynamic_cast
<
const
Circle
&>(
b
)
);
}
double
intersectCircleRect
(
const
Figure
&
a
,
const
Figure
&
b
)
{
return
intersect
(
dynamic_cast
<
const
Circle
&>(
a
)
,
dynamic_cast
<
const
Rect
&>(
b
)
);
}
double
intersectRectCircle
(
const
Figure
&
a
,
const
Figure
&
b
)
{
return
intersect
(
dynamic_cast
<
const
Circle
&>(
b
)
,
dynamic_cast
<
const
Rect
&>(
a
)
);
}
double
intersectRectRect
(
const
Figure
&
a
,
const
Figure
&
b
)
{
return
intersect
(
dynamic_cast
<
const
Rect
&>(
a
)
,
dynamic_cast
<
const
Rect
&>(
b
)
);
}
double
intersect
(
const
Figure
&
a
,
const
Figure
&
b
)
{
enum
{
CIRCLE_INDEX
,
RECT_INDEX
,
N
}
;//indeksy dla typów oraz rozmiar tablicy
typedef
double
(*
PF
)(
const
Figure
&
,
const
Figure
&)
;//typ wskaźnika na funkcję
static
const
PF
CALL_TAB
[
N
][
N
]
=
{//dwuwymiarowa tablica wskaźników na funkcje
{
&
calculateCircleCircle
,
&
calculateCircleRect
}
,
{
&
calculateRectCircle
,
&
calculateRectRect
}
}
;
struct
IndexVisitor
:
public
Visitor
{//pomocniczy wizytator – zwraca indeks dla typu
IndexVisitor
()
:
idx_
(
0
)
{}
virtual
void
visit
(
const
Circle
&
)
{
idx_
=
CIRCLE_INDEX
;
}
virtual
void
visit
(
const
Rectangle
&
)
{
idx_
=
RECT_INDEX
;
}
int
idx_
;
}
visitorA
,
visitorB
;
a
.
accept
(
visitorA
);
b
.
accept
(
visitorB
);
PF
fun
=
CALL_TAB
[
visitorA
.
idx_
][
visitorB
.
idx_
]
;//pobiera wskaźnik z tablicy
return
(*
fun
)(
a
,
b
)
;//woła odpowiednią funkcję
}
24
7/2010
Plik z chomika:
akunseth
Inne pliki z tego folderu:
2010.07_Wielometody Rozszerzenie funkcji wirtualnych_[Programowanie C C++].pdf
(1036 KB)
2010.03_Tworzenie kopii obiektów_[Programowanie C C++].pdf
(362 KB)
Programowanie.rar
(1013251 KB)
C i C++. Bezpieczne programowanie.pdf
(457 KB)
2009.11_Sprytne wskaźniki _[Programowanie C C++].pdf
(498 KB)
Inne foldery tego chomika:
- █▬█ █ ▀█▀ █▬█ █ ▀█▀ Wojskowa Mapa Polski Sztabowa 3 CD ( PL ) 2013
!!! CeZik !!!
!►► Dark Matter [2015]
# Muzyka Filmowa # Soundtrack
•• Elektronika na wesoło
Zgłoś jeśli
naruszono regulamin