Nejprve si zopakujeme, ₧e pod pojmem t°φda v objektov∞ orientovanΘm programovßnφ v jazycφch, jako je C++,
rozumφme urΦitou mno₧inu dat (hodnot) spolu s operacemi, kterΘ lze s t∞mito
daty provßd∞t. Tyto operace se zpravidla oznaΦujφ jako metody. Dopl≥me jeÜt∞, ₧e urΦφme-li n∞jakou
mno₧inu hodnot, musφme takΘ urΦit zp∙sob, jak²m budou tyto hodnoty v pam∞ti
poΦφtaΦe reprezentovßny.
VÜimn∞te si, ₧e tato definice se vlastn∞ kryje s b∞₧nou definicφ
datovΘho typu. T°φdy v C++ tedy p°edstavujφ prost∞ jen dalÜφ datovΘ typy;
jde sice o typy, kterΘ definuje sßm programßtor, ale to na v∞ci nic nem∞nφ;
ostatn∞ t°φdy majφ v C++ prakticky rovnocennΘ postavenφ jako vestav∞nΘ typy.
(Jist∞ jste si uv∞domili, ₧e zde v∙bec nehovo°φme o d∞diΦnosti
a o polymorfizmu. I kdy₧ jde bezpochyby o d∙le₧itΘ vlastnosti, pro naÜe ·vahy
o rozhranφ t°φdy nebudou podstatnΘ.)
Pod rozhranφm rozumφme
v tΘto souvislosti nßstroje, kterΘ umo₧≥ujφ t°φdu pou₧φvat û p°istupovat k
dat∙m, kterß jsou ulo₧ena v jednotliv²ch instancφch nebo ve t°φd∞ jako celku
(statickΘ datovΘ slo₧ky).
Co tedy tvo°φ rozhranφ t°φdy? TΘm∞° automatickß odpov∞∩ na
tuto otßzku znφ, ₧e rozhranφ t°φdy tvo°φ jejφ ve°ejn∞ p°φstupnΘ metody a datovΘ
slo₧ky. M²m cφlem v tomto Φlßnku je ukßzat, ₧e tato odpov∞∩ nenφ ·plnß.
Podφvejme se nejprve na schΘmatick² p°φklad:
class X
{ // N∞jakΘ datovΘ slo₧ky a metody
}
void f(X& x)
{ // à
}
Pat°φ funkce f() do rozhranφ t°φdy X?
NejspφÜ odpovφte, ₧e nikoli, nebo¥ nejde o metodu, ale o
samostatnou funkci. Co kdy₧ ale deklarujeme funkci f() jako sp°ßtelenou,
nap°. takto?
class X
{
// N∞jakΘ datovΘ slo₧ky a metody
áááá friend void
f(X& x);
}
void f(X& x)
{ // ...
}
Funkce f() m∙₧e manipulovat se soukrom²mi daty ulo₧en²mi v
instancφch t°φdy X, nebo¥ jinak bychom ji nepot°ebovali deklarovat jako
sp°ßtelenou; umo₧≥uje tedy t°φdu X pou₧φvat. To ale znamenß, ₧e
je rozumnΘ pova₧ovat ji za souΦßst rozhranφ tΘto t°φdy.
Poj∩me jeÜt∞ o krok dßl: Musφ takovß funkce b²t sp°ßtelenß?
Ne₧ odpovφme, podφvßme se na p°φklad; poslou₧φ nßm t°φda
cplx
p°edstavujφcφ komplexnφ Φφsla. Je samoz°ejmΘ, ₧e souΦßstφ jejφ implementace
bude i p°etφ₧en² operßtor << pro v²stup.
Lze navrhnout nejmΘn∞ dv∞ varianty implementace tohoto operßtoru.
V prvnφ z nich deklarujeme operßtor pro v²pis jako sp°ßtelenou funkci:
// Prvnφ
implementace: operßtor << jako friend
#include <iostream>
using std::ostream;
using std::cout;
using std::endl;
class cplx
{
double re, im;
public:
cplx(double r = 0, double i = 0): re(r),
im(i){}
double& Re(){return re;}
double& Im(){return im;}
friend
ostream& operator<<(ostream& proud, cplx c)
{
return proud << "(" <<
c.re << ", " << c.im << ")";
}
};
Ve druhΘ implementaci se deklaraci friend vyhneme tφm,
₧e ve t°φd∞ cplx deklarujeme ve°ejn∞ p°φstupnou metodu vypis(),
kterß se postarß o vÜe pot°ebnΘ, a v operßtoru <<á se na ni odvolßme.
// Druhß
implementace: nepou₧φvß friend
class cplx
{
double re, im;
public:
cplx(double r = 0, double i = 0): re(r),
im(i){}
double& Re(){return re;}
double& Im(){return im;}
ostream&
vypis(ostream& proud) {
return proud << "(" <<
re << ", " << im << ")";
}
};
ostream&
operator<<(ostream& proud, cplx c)
{
return
c.vypis(proud);
}
Implementace operßtoru << pro v²pis komplexnφch
Φφsel je sice pon∞kud jednoduÜÜφ, ne₧ by bylo pro praktickΘ pou₧itφ pot°ebnΘ,
ale pro naÜe ·Φely pln∞ postaΦuje.
Rozdφl mezi t∞mito dv∞ma implementacemi t°φdy cplx
je minimßlnφ; v obou p°φpadech lze oprßvn∞n∞ tvrdit, ₧e operßtor <<
je souΦßstφ rozhranφ t°φdy cplx. To znamenß, ₧e souΦßstφ rozhranφ t°φdy m∙₧e b²t
i samostatnß funkce, kterß nenφ deklarovßna jako sp°ßtelenß, pokud mß tuto
t°φdu jako parametr.
V p°φpad∞ operßtoru << jsme nem∞li jinou mo₧nost
ne₧ deklarovat jej jako samostatnou funkci, nebo¥ pravidla jazyka C++ jasn∞
°φkajφ, ₧e pro jak²koli p°et∞₧ovateln² binßrnφ operßtor @
znamenß zßpis
a @ b
volßnφ bu∩ funkce operator@(a, b), nebo metody a.operator@(b).
Kdybychom cht∞li definovat operßtor << jako metodu, museli bychom
jej p°idat do knihovnφ t°φdy ostream û ale m∞nit standardnφ
knihovny nenφ nejlepÜφ nßpad.
V n∞kter²ch p°φpadech si ale m∙₧eme vybrat. Jako p°φklad
vezmeme op∞t t°φdu cplx a vybavφme ji navφc operßtorem + pro sΦφtßnφ instancφ.
Implementace operßtoru + jako metody je na prvnφ pohled logiΦt∞jÜφ,
nebo¥ jde o operaci nad daty instance:
// Implementace
operßtoru + jako metody
class cplx
{
double re, im;
public:
cplx(double r = 0, double i = 0): re(r),
im(i){}
double& Re(){return re;}
double& Im(){return im;}
ostream& vypis(ostream& proud)
{
return
proud << "(" << re << ", " <<
im << ")";
}
cplx
operator+(cplx c)
{
return cplx(re+c.re, im+c.im);
}
};
P°esto mß tato implementace nejmΘn∞ jednu zßva₧nou nev²hodu:
Operandy v nφ nemajφ symetrickΘ postavenφ. O tom se snadno p°esv∞dΦφme, deklarujeme-li
prom∞nnΘ
cplx c, a(1,2);
a napφÜeme-li p°φkazy
c = a+1; áááá // OK
resp.
c = 1+a; áááá // Chyba
Prvnφ p°φkaz se p°elo₧φ bez problΘm∙, nebo¥ p°ekladaΦ najde
ve t°φd∞ cplx
metodu operator+()
a jejφ parametr konvertuje na typ cplx pomocφ konstruktoru tΘto t°φdy.
OvÜem druh² p°φkaz p°ekladaΦ oznaΦφ jako chybn², nebo¥ nenajde odpovφdajφcφ
operßtor û nelze nejprve implicitn∞ konvertovat Φφslo 1 typu int
na typ cplx
a pak volat metodu takto vytvo°enΘ instance.
To ovÜem odporuje naÜφ b∞₧nΘ zkuÜenosti: Z matematiky jsme
zvyklφ, ₧e sΦφtßnφ je komutativnφ, ₧e m∙₧eme po°adφ jednotliv²ch sΦφtanc∙
libovoln∞ zam∞≥ovat.
M∙₧eme si sice pomoci explicitnφm p°etypovßnφm,
c = cplx(1)+a;
avÜak ani tento zßpis nenφ p°φliÜ intuitivnφ.
To znamenß, ₧e vhodn∞jÜφ bude pou₧φt implementaci operßtoru
+ jako samostatnΘ funkce. Pou₧ijeme op∞t triku zalo₧enΘho na ve°ejn∞ p°φstupnΘ
metod∞, kterß se postarß o vÜe pot°ebnΘ:
// Implementace
operßtoru + jako samostatnΘ funkce
class cplx
{
double re, im;
public:
cplx(double r = 0, double i = 0): re(r),
im(i){}
double& Re(){return re;}
double& Im(){return im;}
ostream& vypis(ostream& proud)
{
return
proud << "(" << re << ", " <<
im << ")";
}
cplx
Plus(cplx c)
{
return cplx(re+c.re, im+c.im);
}
};
cplx
operator +(cplx a, cplx b)
{
return
a.Plus(b);
}
Tentokrßt p°ijme p°ekladaΦ oba p°φkazy
c = a+1; áááá // stßle OK
c = 1+a; áááá // Nynφ takΘ OK
bez nßmitek.
Podobn∞ i p°i deklaraci dalÜφch operßtor∙ implementujφcφch
b∞₧nΘ aritmetickΘ operace s komplexnφmi Φφsly zjistφme, ₧e je rozumnΘ deklarovat
je jako samostatnΘ funkce. Ukazuje se tedy, ₧e p°φpad∙, kdy je nutnΘ nebo
v²hodnΘ deklarovat n∞kterou z operacφ s daty t°φdy nebo instance jako samostatnou
funkci, m∙₧e b²t vφce, ne₧ se na prvnφ pohled zdß.
TakΘ v tomto p°φpad∞ je naprosto logickΘ tvrdit, ₧e operßtor
+, stejn∞ jako ostatnφ aritmetickΘ operßtory, je souΦßstφ rozhranφ t°φdy cplx,
a to bez ohledu na to, zda je implementovßn jako metoda nebo jako samostatnß
funkce.
Podobn²mi ·vahami dosp∞jeme k zßv∞ru, ₧e za souΦßsti rozhranφ
t°φdy X m∙₧eme pova₧ovat i samostatnΘ funkce, kterΘ vracejφ
instance tΘto t°φdy nebo kterΘ ji n∞jak²m jin²m zp∙sobem äzmi≥ujφô.
Z p°edchozφch ·vah by se mohlo zdßt, ₧e souΦßstφ rozhranφ
t°φdy X
je jakßkoli funkce, kterß mß parametr typu X nebo kterß t°φdu X
jak²mkoli jin²m zp∙sobem äzmi≥ujeô. P°edstavme si ale, ₧e X
je knihovnφ t°φda ostream. Mß smysl pova₧ovat za souΦßst jejφho rozhranφ
funkci operator<<(ostream&, cplx), kterou napφÜeme ve svΘm
programu pro naÜi vlastnφ t°φdu cplx?
TakovΘ pojetφ pojmu rozhranφ by nejspφÜ vedlo ke zmatk∙m,
nebo¥ pak by se rozhranφ t°φdy mohlo m∞nit od programu k programu, a to je
(p°inejmenÜφm v p°φpad∞ knihovnφch t°φd) ne₧ßdoucφ.
Rozumn∞jÜφ je zahrnout do rozhranφ t°φdy X
pouze ty samostatnΘ funkce, kterΘ nejen pracujφ s X, ale takΘ se spolu
s touto t°φdou dodßvajφ. Ostatn∞ takovΘto funkce budou spolu s nφ takΘ dokumentovßny.
Shrneme-li v²sledky p°edchozφch ·vah, dosp∞jeme k tzv. principu rozhranφ [1]:
Podφvejme se nynφ, jak do p°edchozφch ·vah zapadajφ prostory
jmen.
Minule jsme si °ekli, ₧e na prostory jmen se m∙₧eme dφvat
jako na p°φjmenφ p°ipojovanß k identifikßtor∙m, kterß pomßhajφ zajistit v
rozsßhl²ch programech jejich jednoznaΦnost. OvÜem tφm takΘ usnad≥ujφ organizaci
programu: V∞ci, kterΘ tvo°φ logick² celek, uklßdßme do stejnΘho prostoru jmen.
Uka₧me si, jak m∙₧eme uspo°ßdat naÜi miniknihovnu pro komplexnφ Φφsla.
T°φdu cplx a funkce, kterΘ s nφ souvisφ, deklarujeme v prostoru
jmen komplex.
Do hlaviΦkovΘho souboru cplx.h ulo₧φme deklaraci t°φdy a prototypu obou samostatn²ch
funkcφ:
// Soubor cplx.h
#ifndef _KOMPLEX_H_
á #define _KOMPLEX_H_
#include <iostream>
namespace komplex {
using std::ostream;
class cplx
{
double
re, im;
public:
cplx(double
r = 0, double i = 0): re(r), im(i){}
double&
Re(){return re;}
double&
Im(){return im;}
ostream&
Vypis(ostream& proud) {
return
proud << "(" << re << ", " <<
im << ")";
}
cplx
Plus(cplx c)
{
return
cplx(re+c.re, im+c.im);
}
};
cplx operator +(cplx a, cplx b);
ostream& operator<<(ostream&
proud, cplx c);
}
#endif
Do souboru cplx.cpp ulo₧φme definice obou
samostatn∞ deklarovan²ch operßtor∙.
// Soubor cplx.cpp
#include "cplx.h"
namespace komplex {
cplx operator +(cplx a, cplx b)
{
return
a.Plus(b);
}
ostream& operator<<(ostream&
proud, cplx c)
{
return
c.Vypis(proud);
}
}
Do tohoto souboru pat°φ samoz°ejm∞ i definice vÜech metod,
kterΘ nejsou vlo₧enΘ (inline). NaÜe zjednoduÜenß implementace t°φdy komplex::cplx
vÜak zatφm ₧ßdnΘ takovΘto metody neobsahuje.
Nynφ m∙₧eme v programu, kter² pracuje s t°φdou komplex::cplx,
napsat nap°.
#include "cplx.h"
using komplex::cplx;
using std::cout;
// ...
cplx a(1,2);
cout << 1+a;
Dφky pravidlu vyhledßvßnφ funkcφ i v prostorech jmen parametr∙
(Koenigovu vyhledßvßnφ) nenφ t°eba zp°φstup≥ovat operßtory + a <<.
P°edchozφ p°φklad ukazuje, ₧e Koenigovo vyhledßvßnφ velmi
dob°e dopl≥uje princip rozhranφ. Logickou souvislost t°φdy a spolu s nφ dodßvan²ch
samostatn²ch funkcφ, kterΘ dopl≥ujφ jejφ rozhranφ, lze podtrhnout tφm, ₧e
t°φdu i tyto funkce umφstφme do spoleΦnΘho prostoru jmen.
Prostory jmen zdßnliv∞ umo₧≥ujφ klientsk²m programßtor∙m,
tj. u₧ivateli t°φdy deklarovanΘ v prostoru jmen, nestarat se o samostatnΘ
funkce a operßtory deklarovanΘ spolu s t°φdou. Zdßnφ ovÜem m∙₧e klamat. Podφvejme
se nynφ na p°φpad, kdy programßtor pou₧φvajφcφ t°φdu X
deklaruje funkci se stejn²m prototypem, jako mß jedna z funkcφ doprovßzejφcφch
tuto t°φdu.
namespace Alfa
{
class X{};
void f(X x){ /*...*/ }áááá // (1)
}
void f(Alfa::X x){ /*...*/ }áááá
// (2)
int main()
{
Alfa::X x;
f(x) áááá áááá //
KterΘ f()?
return 0;
}
Volßnφ funkce f() ve funkci main()
je nejednoznaΦnΘ, nebo¥ vyhledßvacφ mechanizmus najde krom∞ globßlnφ funkce
oznaΦenΘ v komentß°i Φφslem (1) jeÜt∞ i funkci (2) v prostoru jmen Alfa.
Na prvnφ pohled se zdß, ₧e jde o nevφtan² d∙sledek Koenigova
vyhledßvßnφ. Je ale opravdu tak ne₧ßdoucφ? Podφvejme se op∞t na konkrΘtnφ
p°φklad: Vezm∞me naÜi oblφbenou t°φdu komplex::cplx a do role funkce
f()
dosa∩me operßtor <<. Dostaneme n∞co jako
#include <iostream>
using std::ostream;ááá
namespace komplex {
class cplx
{áááá //
T°φda cplx stejnß jako p°edtφm
};
cplx operator +(cplx a, cplx b);
ostream& operator<<(ostream&
proud, cplx c);
}
ostream& operator<<(ostream& proud, komplex::cplx c)
{
return c.vypis(proud);
}
int main()
{
using komplex::cplx;
using std::cout;
cplx a(1,2);
cout
<< 1+a;á áááá // Chyba û nejednoznaΦnΘ
return 0;
}
a p°ekladaΦ ohlßsφ, ₧e narazil na nejednoznaΦnost p°i vyhledßvßnφ
operßtoru <<. Na tomto p°φkladu je ale naprosto z°ejmΘ,
₧e klientsk² programßtor p°ehlΘdl, ₧e s t°φdou komplex::cplx se dodßvß ji₧ hotov²
operßtor <<, a pokusil se naprogramovat si ho sßm. Tφm,
₧e zde p°ekladaΦ ohlßsφ chybu, jej na toto nedopat°enφ upozornφ.
M∙₧e se samoz°ejm∞ stßt, ₧e klientskΘmu programßtorovi nevyhovuje
p∙vodnφ implementace n∞kterΘ ze samostatn²ch funkcφ, dopl≥ujφcφch rozhranφ
t°φdy, a ₧e si ji chce naprogramovat sßm. P∙jde-li o obyΦejnou (nikoli operßtorovou)
funkci, nenφ t°eba p∙vodnφ program p°φliÜ m∞nit, staΦφ p°i volßnφ kvalifikovat
jejφ identifikßtor prostorem jmen.
Uka₧me si schΘmatick² p°φklad:
namespace Alfa
{
class X{};
void f(X x){ /*...*/ }áááá // (1)
}
int main()
{
ááááá Alfa::X x;
::f(x);á áááá áááá // Volß (2)
á áááá komplex::f(x);ááá áááá //
Volß (1)
return 0;
}
V p°φpad∞ operßtor∙ nelze kvalifikaci pou₧φt. Pak nezb²vß,
ne₧ se uch²lit k funkcionßlnφmu zßpisu operßtoru, nap°.
komplex::operator<<(cout, c);
To je sice velmi nepohodlnΘ, ale na druhΘ stran∞ nejde v
₧ßdnΘm p°φpad∞ o b∞₧nou situaci.
P°φklady, kterΘ doprovßzely naÜe ·vahy, ukazujφ, ₧e princip
rozhranφ a Koenigovo vyhledßvßnφ se vzßjemn∞ dopl≥ujφ. OvÜem jako vÜechny
nßstroje v C++, i tyto dva mohou p°i nevhodnΘm pou₧itφ zp∙sobit problΘmy.
Odkaz:
1. Herb Sutter: Exceptional C++. Addison-Wesley 2000.