V tomto p°φsp∞vku si povÜimneme zßle₧itostφ, kterΘ proÜly tak bou°liv²m v²vojem, ₧e p°i tvorb∞ standardu byly n∞kolikrßt m∞n∞ny a nakonec doÜlo i na zm∞nu jejich sΘmantiky, tak₧e chyb p°i programovßnφ je dost a dost. Snad se te∩ alespo≥ n∞kter²m vyhnete...
Kde se vzala slo₧itost C++
N∞kde na poΦßtku byl jazyk C. Jazyk to byl dobr², pou₧φvß se dodnes. Byl ale poznamenßn dobou svΘho vzniku, kdy objektov∞ orientovanΘ programovßnφ teprve Φekalo na svΘ objevenφ. Prßv∞ o n∞ se "cΘΦko" rozhodl rozÜφ°it Bjarne Stroustrup - vznikl jazyk C++. Ale ani to zanedlouho nestaΦilo a doÜla °ada i na generickΘ programovßnφ. A tak C++ rostlo, rostlo, a₧ sv²m tv∙rc∙m trochu p°erostlo p°es hlavu (o programßtorech, kte°φ tento jazyk pou₧φvajφ, ani nemluv∞). Dokladem m∙₧e b²t jeho mezinßrodnφ standard ISO/IEC 14882:1998 - vφce ne₧ 700 stran pln²ch slo₧it²ch a n∞kdy tΘm∞° nepochopiteln²ch pravidel mluvφ samo za sebe. Nenφ se co divit, ₧e i tv∙rci p°ekladaΦ∙ majφ co d∞lat, aby sprßvn∞ pochopili a implementovali po₧adovanΘ vlastnosti.
ProΦ takovß slo₧itost? Odpov∞∩ nenφ tak t∞₧kß. Pokud majφ vedle sebe existovat r∙znß (ale opravdu r∙znß) programovacφ paradigmata, je t°eba oÜet°it jejich "styΦnΘ plochy". Vezm∞me t°eba objektov∞ orientovanΘ programovßnφ a generickΘ programovßnφ. Prvnφ p°edstavuje znovupou₧itelnost na ·rovni binßrnφch komponent, kde₧to druhΘ znovupou₧itelnost na ·rovni zdrojovΘho k≤du, tedy n∞co zcela odliÜnΘho. Jsou-li pak ob∞ souΦßstmi jednoho programovacφho jazyka, je kv∙li konzistenci nutnΘ zavΘst spoustu pravidel regulujφcφch vzßjemn² vztah obou paradigmat. A k tomu C++ jeÜt∞ navφc podporuje procedurßlnφ (strukturovanΘ) programovßnφ, ÜablonovΘ metaprogramovßnφ a kdovφ co jeÜt∞ - asi u₧ je jasnΘ, kde se vzalo t∞ch 700 stran a proΦ je to tak slo₧itΘ.
V tΘto souvislosti je vhodnΘ zmφnit jazyky Java a C#. Zde Üli auto°i jinou cestou. Prost∞ zvolili programovacφ paradigma, kterΘ je zrovna (komerΦn∞) nej·sp∞Ün∞jÜφ, a ta ostatnφ bu∩ vypustili ·pln∞ (jako se to stalo Üablonßm), nebo omezili do pat°iΦn²ch mezφ (t°eba unsafe k≤d v C#). Proto jsou tyto jazyky "pr∙hledn∞jÜφ" ne₧ C++. (Je to samoz°ejm∞ pohled trochu zjednoduÜen²; vφc se dozvφte nap°. v [4].)
Ale poj∩me zp∞t k C++ a proberme si jednu z jeho "lah∙dek" - st°et hned t°φ paradigmat: generickΘho programovßnφ (Üablona t°φdy, funkce), objektov∞ orientovanΘho programovßnφ (deklarace friend) a procedurßlnφho programovßnφ (obyΦejnß funkce). Tato Φßst byla v pr∙b∞hu standardizace n∞kolikrßt zm∞n∞na, p°iΦem₧ neÜlo jen o p°idßnφ novΘ syntaxe, ale i o zm∞nu v²znamu n∞kter²ch d°φve pou₧φvan²ch konstrukcφ. SluÜφ se proto upozornit ty, kdo pou₧φvajφ voln∞ dostupnou pracovnφ verzi standardu z roku 1996, ₧e tato Φßst je ve finßlnφ verzi zm∞n∞na.
N∞co je zase jinak...
V²klad zaΦneme trochu netradiΦn∞ Φastou chybou, kterß je d∙sledkem zm∞ny sΘmantiky deklarace friend. Tuto chybu nejdete i v autorov∞ "typickΘ" implementaci vektoru v Φlßnku o Üablonßch v²raz∙ [3]. P°itom jeÜt∞ p°ed n∞kolika lety by bylo vÜe v po°ßdku. Za toto "nedopat°enφ, ke kterΘmu dochßzφ nejv²Üe jednou za 10 let..." se autor Φtenß°∙m omlouvß.
Nejd°φve si p°ipome≥me onu zmi≥ovanou typickou implementaci vektoru:
template <class T>
class Vektor
{
public:
// ...
friend
Vektor<T> operator +(
const Vektor<T> & v1,
const Vektor<T> & v2);
private:
T * data_;
int size_; // velikost
};
// Üablona globßlnφho operßtoru +
template <class T>
Vektor<T> operator +(
const Vektor<T> & v1,
const Vektor<T> & v2)
{
Vektor<T> pom(v1.size_);
for (int i = 0; i < pom.size_; ++i)
{
pom.data_[i] =
v1.data_[i] + v2.data_[i];
}
return pom;
}
Konstruktor, destruktor a ostatnφ metody jist∞ dokß₧ete doplnit sami. Nezapome≥te na kopφrovacφ konstruktor, ten zde hraje d∙le₧itou roli - provßdφ tzv. hlubokou kopii (deep copy), kterß je nutnß pro sprßvnou funkci nap°. operßtoru sΦφtßnφ (p°φkaz return pom;).
Tento k≤d je syntakticky v po°ßdku - to znamenß, ₧e jej lze bez potφ₧φ p°elo₧it. ProblΘm nastane, kdy₧ n∞kde dßl budeme chtφt sp°ßtelen² operßtor sΦφtßnφ pou₧φt:
Vektor<double> a;
Vektor<double> b;
// ...
a + b; // !
Ve starÜφch p°ekladaΦφch (nap°. Borland C++ 5.0) to projde bez povÜimnutφ a vÜe funguje. NovΘ p°ekladaΦe, kterΘ se °φdφ standardem ISO jazyka C++ (nap°. Borland C++ Builder 4.0 nebo 5.0), k≤d sice p°elo₧φ, ale nedokß₧φ ho slinkovat. Objevφ se chybovΘ hlßÜenφ o tom, ₧e linkovacφ program nem∙₧e nalΘzt operßtor
Vektor<double> operator+(
const Vektor<double> &,
const Vektor<double> &);
Na prvnφ pohled to vypadß jako chyba p°ekladaΦe. Co je tedy jinak? Abychom na to dokßzali odpov∞d∞t, musφme nejd°φve podniknout v²zkumnou v²pravu do on∞ch 700 stran standardu C++. Zajφmat nßs bude v²znam deklarace friend, a to v souvislosti se Üablonami.
Cesta do hlubin standardu C++
Zde jsou zßkladnφ pravidla (standard [1], sekce 14.5.3.1):
P°φtelem t°φdy nebo ÜablonovΘ t°φdy m∙₧e b²t Üablona t°φdy Φi funkce, specializace Üablony t°φdy Φi funkce nebo obyΦejnß (neÜablonovß) funkce Φi t°φda. Pro deklaraci sp°ßtelenΘ funkce, kterß nenφ Üablonovou deklaracφ, platφ:
[1] jestli₧e jmΘno sp°ßtelenΘ funkce je kvalifikovanΘ nebo nekvalifikovanΘ id-Üablony, pak deklarace odpovφdß danΘ specializaci ÜablonovΘ funkce, jinak
[2] jestli₧e jmΘno sp°ßtelenΘ funkce je kvalifikovanΘ a p°φsluÜnß neÜablonovß funkce je nalezena ve specifikovanΘ t°φd∞ nebo prostoru jmen, pak deklarace odpovφdß tΘto funkci, jinak
[3] jestli₧e jmΘno sp°ßtelenΘ funkce je kvalifikovanΘ a ve specifikovanΘ t°φd∞ nebo prostoru jmen je nalezena odpovφdajφcφ specializace ÜablonovΘ funkce, pak deklarace odpovφdß tΘto specializaci, jinak
[4] musφ jφt o nekvalifikovanΘ jmΘno, kterΘ deklaruje (nebo re-deklaruje) obyΦejnou (neÜablonovou) funkci.
Abychom se v takovΘhle hr∙ze mohli vyznat, nejd°φve si trochu objasn∞me terminologii.
* Vysv∞tlenφ pojmu specializace Üablony naleznete v Φlßnku [2].
* JmΘna mohou b²t kvalifikovanß nebo nekvalifikovanß. Operßtor kvalifikace je :: ("Φty°teΦka"). ZjednoduÜen∞ °eΦeno, pokud je souΦßstφ jmΘna operßtor ::, jde o kvalifikovanΘ jmΘno. P°φklady:
neco // nekvalifikovanΘ jmΘno
::neco // vÜechna ostatnφ jsou kvalifikovanß
MojeTrida::neco
ProstorJmen::neco
ProstorJmen::MojeTrida::neco
* id-Üablony je jmΘno Üablony nßsledovanΘ seznamem Üablonov²ch argument∙ v lomen²ch zßvorkßch. Pokud majφ n∞kterΘ ÜablonovΘ parametry deklarovßny implicitnφ argumenty, je mo₧nΘ je vynechat (podle analogick²ch pravidel jako u implicitnφch argument∙ funkcφ); nesmφme vÜak vynechat lomenΘ zßvorky - i kdy₧ jsou prßzdnΘ. P°φklady:
neco<T> // nekvalifikovanΘ id-Üablony
::MojeTrida<double, 3>
ProstorJmen::neco<>
A te∩ prakticky...
Nynφ si projd∞te uvedenß pravidla jeÜt∞ jednou a pokuste se vy°eÜit nßsledujφcφ ·kol. Mßme n∞kolik deklaracφ funkcφ oznaΦen²ch (1) a₧ (5) a Üablonovou t°φdu Trida, ve kterΘ jsou deklarace sp°ßtelen²ch t°φd a funkcφ. Pokuste se vysv∞tlit, co jednotlivΘ deklarace znamenajφ, a v p°φpad∞ deklaracφ sp°ßtelen²ch funkcφ urΦit, kterß z deklaracφ (1) a₧ (4) je jimi myÜlena, p°φpadn∞ podle kterΘho z pravidel [1] a₧ [4] bylo rozhodnuto.
(d) Deklarace sp°ßtelenΘ funkce; pou₧ijeme tedy v²Üe uvedenß pravidla. Jednß se o nekvalifikovanΘ id-Üablony, pou₧ije se proto pravidlo [1] a deklarace (1). Znamenß to tedy jistΘ provßzßnφ Üablonov²ch argument∙: funkce f<T> je sp°ßtelenß s t°φdou Trida<T>.
(e) Stejn∞ jako (c) s jedin²m rozdφlem - jde o kvalifikovanΘ id-Üablony.
(f) Zde je situace slo₧it∞jÜφ. Musφme rozliÜit dva p°φpady. Jestli₧e T je int, pak podle pravidla [2] je pou₧ita neÜablonovß funkce (2). Pokud T nenφ int, pak podle pravidla [3] jde o funkci (1). (Vzhledem ke zkuÜenostem s b∞₧n²mi p°ekladaΦi berte toto zatφm jako sci-fi.)
(g) NekvalifikovanΘ jmΘno; podle pravidla [4] je to neÜablonovß funkce f. Jde o obyΦejnou (neÜablonovou) funkci, jejφ₧ parametry p°φmo zßvisφ na Üablonov²ch parametrech t°φdy Trida. To znamenß, ₧e deklarace (2) pokr²vß pouze jednu mo₧nost, kdy T je int. Kdyby T byl jak²koliv jin² typ, museli bychom dodefinovat p°φsluÜnou neÜablonovou funkci.
(h) Analogicky jako v bodu (c); pravidlo [1] urΦφ deklaraci (3).
(i) Analogicky jako v bodu (d); pravidlo [1] urΦφ deklaraci (3).
(j) KvalifikovanΘ jmΘno; nenφ nalezena ₧ßdnß vhodnß neÜablonovß funkce, tak₧e podle pravidla [3] se jednß o deklaraci (3), tj. pou₧ije se specializace ÜablonovΘ funkce.
(k) NekvalifikovanΘ jmΘno; podle pravidla [4] je to neÜablonovß funkce g (dokonce jejφ deklarace). Takovß funkce tu vÜak nenφ definovßna. P°esto nemusφ jφt o chybu - stejn∞ jako v p°φpad∞ (g) deklarace friend °φkß, ₧e pokud takovß funkce existuje, pak je sp°ßtelenß - nic vφc. Stejn∞ jako v p°φpad∞ (f) je zde zßvislost parametr∙ funkce na Üablonov²ch parametrech t°φdy Trida.
(l) Sp°ßtelenou funkcφ je obyΦejnß °adovß funkce; pravidlo [4], deklarace (5). Jeliko₧ parametry tΘto funkce v∙bec nesouvisφ se Üablonov²mi parametry t°φdy Trida, je tato funkce sp°ßtelenß s jakoukoliv specializacφ t°φdy Trida.
(m) Deklarujeme sp°ßtelenou Üablonovou funkci h; deklarace (4). Zde se pravidla [1] a₧ [4] nepou₧φvajφ, nebo¥ jde o Üablonovou deklaraci. VÜechny specializace ÜablonovΘ funkce h jsou sp°ßtelenΘ s jakoukoliv specializacφ ÜablonovΘ t°φdy Trida.
Co je jeÜt∞ dobrΘ v∞d∞t
Deklarace sp°ßtelenΘ funkce, kterß nenφ zßrove≥ Üablonovou deklaracφ a ve kterΘ je jmΘno funkce nekvalifikovanΘ id-Üablony, musφ odkazovat na Üablonu funkce v nejbli₧Üφm nad°φzenΘm prostoru jmen.
template <class T> void f(T);
void g(int); // alias ::g
namespace N
{
template <class T> void h(T);
class A
{
// ...
friend void f<>(int); // nelze
friend void h<>(int); // OK, N::h
friend void g(int); // OK,
// deklarace funkce N::g, nikoli ::g
};
}
VÜimn∞te si takΘ, ₧e deklarace sp°ßtelenΘ funkce g (ve skuteΦnosti N::g) nemß s globßlnφ funkcφ g nic spoleΦnΘho.
Jako p°φtele m∙₧eme deklarovat Üablonu funkce - p°φpad (m), nebo t°φdy - p°φpad (b). Je zde jeden rozdφl: p°φsluÜnou funkci smφme definovat uvnit° t°φdy (tedy na mφst∞ deklarace friend), kde₧to sp°ßtelenou t°φdu ne.
template <class T> friend class B; // pouze deklarace
};
èablona funkce Φi t°φdy nem∙₧e b²t p°φtelem lokßlnφ t°φdy, tj. t°φdy definovanΘ uvnit° n∞jakΘ funkce.
Pokud deklarace sp°ßtelenΘ funkce odkazuje na specializaci ÜablonovΘ funkce, nesmφ se v deklaraci parametr∙ vyskytnout implicitnφ argumenty ani nesmφ b²t pou₧ito klφΦovΘ slovo inline.
Jak to tedy bylo
Nynφ se vrßtφme k naÜemu problΘmu z ·vodu Φlßnku. Ve sv∞tle nov²ch poznatk∙ m∙₧eme °φci, ₧e deklarace friend deklaruje °adovou funkci, nikoli specializaci Üablony; viz pravidlo [4]. To znamenß, ₧e p°ekladaΦ nepou₧ije nßmi nabφzenou Üablonu operßtoru + k vytvo°enφ specializace a oΦekßvß, ₧e si pot°ebnou operßtorovou funkci napφÜeme sami. To jsme vÜak neud∞lali, a proto ji linkovacφ program nenaÜel. Museli bychom dodefinovat °adovou neÜablonovou funkci
Vektor<double> operator+(
const Vektor<double> &,
const Vektor<double> &);
Ale co kdy₧ pou₧ijeme Vektor<int>? Nastane zase ta samß chyba. Museli bychom jeÜt∞ napsat operßtorovou funkci
Vektor<int> operator+(
const Vektor<int> &,
const Vektor<int> &);
A tak dßle pro vÜechny mo₧nΘ typy... Je jist∞ jasnΘ, ₧e takhle jsme to necht∞li. Musφme proto provΘst n∞kolik ·prav. Pro snazÜφ orientaci budou tyto ·pravy v dalÜφm textu zv²razn∞ny.
╪eÜenφ 1
Provedeme propojenφ Üablonov²ch argument∙ pou₧itφm id-Üablony v deklaraci friend.
// p°edb∞₧nΘ deklarace
template <class T> class Vektor;
template <class T>
Vektor<T> operator +(
const Vektor<T> & v1,
const Vektor<T> & v2);
// upravenß t°φda Vektor
template <class T>
class Vektor
{
public:
// ...
friend
Vektor<T> operator +<T>(
const Vektor<T> & v1,
const Vektor<T> & v2);
private:
T * data_;
int size_; // velikost
};
èablona operßtoru + z∙stane beze zm∞ny. Rozdφl je tedy v tom, ₧e p°i deklaraci sp°ßtelenΘ funkce uvedeme explicitn∞ ÜablonovΘ argumenty, tj. mφsto operator+ napφÜeme operator+<T>. Tφm se provß₧e specializace t°φdy Vektor se specializacφ operßtoru +. P°itom je nutnΘ poskytnout p°ekladaΦi p°edem informace o tom, ₧e mßme Üablonu operßtoru +. To jsou ony p°edb∞₧nΘ deklarace. Nynφ vÜe funguje tak, jak jsme cht∞li.
╪eÜenφ 2
P°edchozφ °eÜenφ p°edpoklßdß, ₧e p°φsluÜn² operßtor + bude pouze specializace Üablony. Pokud bychom pro n∞jak² typ m∞li k dispozici odpovφdajφcφ neÜablonovou operßtorovou funkci, mßme sm∙lu. Ale ne tak docela - m∙₧eme na to jφt trochu jinak:
// p°edb∞₧nΘ deklarace
template <class T> class Vektor;
// Üablona operßtoru +
template <class T>
Vektor<T> operator +(
const Vektor<T> & v1,
const Vektor<T> & v2);
// neÜablonov² operßtor +
Vektor<int> operator +(
const Vektor<int> & v1,
const Vektor<int> & v2);
// upravenß t°φda Vektor
template <class T>
class Vektor
{
public:
// ...
friend
Vektor<T> ::operator +(
const Vektor<T> & v1,
const Vektor<T> & v2);
private:
T * data_;
int size_; // velikost
};
Finta spoΦφvß v pou₧itφ kvalifikovanΘho jmΘna (a nepou₧itφ id-Üablony). Nynφ do hry vstupujφ pravidla [2] a [3]. Pro t°φdu Vektor<int> je podle pravidla [2] nalezena neÜablonovß operßtorovß funkce. Pro vÜechny ostatnφ specializace t°φdy Vektor se pou₧ije Üablona operßtoru + podle pravidla [3].
JeÜt∞ poznamenejme, ₧e pot°eba pou₧itφ neÜablonovΘ funkce namφsto Üablony b²vß Φasto spojena s optimalizacφ. èablona je sama o sob∞ dost obecnß zßle₧itost a pro n∞kterΘ specifickΘ typy m∙₧e b²t lepÜφ naprogramovat dan² ·kol jinak. Je sice mo₧nΘ pou₧φt explicitnφ specializaci, ale n∞kdy snad m∙₧e b²t dobrΘ pou₧φt neÜablonovou funkci. Jazyk C++ nenutφ pou₧φvat to Φi ono, ale pokud jedno z toho pou₧ijeme, mßme jistotu, ₧e si s tφm dokß₧e poradit (alespo≥ teoreticky).
Zßv∞r
Pokud nepou₧ijeme Üablony, je p°ßtelstvφ v C++ pom∞rn∞ snadnou zßle₧itostφ. Jakmile do hry vstoupφ Üablony, je t°eba dßvat velk² pozor na to, co jednotlivΘ deklarace p°ßtel znamenajφ. Jeliko₧ doÜlo ke zm∞n∞ v²znamu n∞kter²ch konstrukcφ, mohou se objevit problΘmy p°i kompilaci (a linkovßnφ) starÜφch program∙.
Typick²m p°φkladem je v²Üe zmφn∞nß implementace t°φdy a sp°ßtelenΘho operßtoru. Vzhledem ke stavu dneÜnφch p°ekladaΦ∙ je lΘpe pou₧φt "╪eÜenφ 1", tj. provßzat ÜablonovΘ argumenty.
A nakonec jeÜt∞ malß poznßmka k tΘ p∙vodnφ "typickΘ" implementaci. I kdy₧ tam p°ipφÜeme neÜablonovou definici pot°ebnΘho operßtoru, majφ s tφm p°ekladaΦe Borland C++ Builder problΘmy. Verze 4 ohlßsφ chybu ji₧ p°i generovßnφ (!) k≤du a pokroΦilejÜφ verze 5 obΦas skonΦφ se zßhadnou chybou p°i linkovßnφ. Naproti tomu Microsoft Visual C++ 6.0 s tφm problΘmy nemß.
Ukßzky k≤du naleznete na Chip CD 10/01 v rubrice Chip Plus. N∞kterΘ v∞ci vÜak bohu₧el jeÜt∞ nefungujφ tak, jak by podle standardu m∞ly...
Jaroslav Fran∞k
Literatura
[1] Standard C++: International standard ISO/IEC 14882, 1998-09-01
[2] M. Virius: èablony po Üesti letech, Chip 12/00
[3] J. Fran∞k: èablony v²raz∙, Chip 4/01
[4] M. Virius: Kafe, m°φ₧ a dva plusy, Chip 7/01 a 8/01