Prostory jmen se objevily ji₧ v neoficißlnφm standardu jazyka C++ [1] a byly pom∞rn∞ jednoduchΘ. P°esto je komerΦnφ p°ekladaΦe tohoto jazyka implementovaly pom∞rn∞ pozd∞, a₧ v polovin∞ devadesßt²ch let minulΘho stoletφ. P°φΦinou byly z°ejm∞ mimo jinΘ nejasnosti kolem jednΘ zdßnlivΘ drobnosti, kterou standard p°idal û vyhledßvßnφ jmen volan²ch funkcφ v zßvislosti na parametrech (tzv. Koenigova vyhledßvßnφ).
Podφvejme se tedy, jak to s prostory jmen v C++ je.
K Φemu jsou prostory
jmen dobrΘ? ZaΦneme zeÜiroka, od poΦφtaΦovΘ prehistorie.
Identifikßtory prvnφch
verzφch jazyka FORTRAN û tehdy se psal se vÜemi pφsmeny velk²mi û mohly mφt
nejv²Üe 6 znak∙; k dispozici bylo 26 pφsmen anglickΘ abecedy a Φφslice 0û9.
(Malß a velkß pφsmena se nerozliÜovala, v∞tÜina poΦφtaΦ∙ znala pouze velkß
pφsmena). Identifikßtor musel zaΦφnat, jak je dodnes obvyklΘ, pφsmenem. Teoreticky
m∞l tedy programßtorá dispozici 26
jednoznakov²ch identifikßtor∙, 26 ╫ 36 dvouznakov²ch, à a 26 ╫ 365 Üestiznakov²ch
identifikßtor∙. To vφce ne₧ 16 miliard r∙zn²ch jmen, a to se autor∙m FORTRANu
zdßlo dostateΦnΘ.
NicmΘn∞ brzy se ukßzalo, ₧e identifikßtory jako X235B1 nevedou k nejp°ehledn∞jÜφm program∙m a pro smysluplnß jmΘna je 6 znak∙ obvykle mßlo. Proto pozd∞jÜφ jazyky û a hlavn∞ jejich p°ekladaΦe û dovolily pou₧φvat podstatn∞ delÜφ identifikßtory.
NicmΘn∞ s r∙stem rozsahu
projekt∙, a takΘ s r∙stem rozsahu programov²ch knihoven, se brzy ukßzalo,
₧e ôsmysluplnΘ identifikßtoryö snadno p∙sobφ problΘmy jinΘho druhu: Dochßzφ
ke konflikt∙m jmen. Pom∞rn∞ Φasto se stane, ₧e r∙znφ ΦlenovΘ v²vojovΘho t²mu
p°id∞lφ r∙zn²m prom∞nn²m nebo funkcφm stejn² identifikßtor nebo ₧e pro n∞
pou₧ijφ identifikßtor, kter² zßrove≥ oznaΦuje funkci, typ nebo prom∞nnou z
n∞kterΘ z programov²ch knihoven. Stejn∞ dob°e se m∙₧e stßt, ₧e v jednom projektu
pou₧ijeme dv∞ r∙znΘ knihovny od dvou r∙zn²ch dodavatel∙, kterΘ shodou okolnostφ
pou₧φvajφ t²₧ identifikßtor pro dv∞ r∙znΘ v∞ci û a problΘm je na sv∞t∞.
N∞kte°φ programßto°i
v jazyce C pr² pou₧φvali pom∞rn∞ jednoduch² trik: SvΘ globßlnφ prom∞nnΘ deklarovali
jako slo₧ky struktury. Nap°φklad takto:
struct {áááá // Globßlnφ struktura
int proud1;
áááá // a dalÜφ prom∞nnΘ
} Vstup;
// ...
int f() {
if(Vstup.proud1)
ZpracujTo();
áááá // ... a dalÜφ p°φkazy
}
M∙₧eme si p°edstavovat,
₧e Vstup
je oznaΦenφ Φßsti programu, kter² vyvφjφ jeden Φlen t²mu. To m∙₧e docela dob°e
fungovat, pokud se ΦlenovΘ tohoto t²mu dohodnou, jak se budou jejich struktury
s prom∞nn²mi jmenovat.
Toto °eÜenφ ovÜem funguje
pouze pro identifikßtory prom∞nn²ch, nikoli pro identifikßtory funkcφ, nebo¥
ty nemohou b²t v jazyce C souΦßstφ struktury.
V C++ bychom mohli do
struktur ukr²t i funkce a datovΘ typy. Tento jazyk nßm ale nabφzφ elegantn∞jÜφ
°eÜenφ û prostory jmen. Na prostor jmen se m∙₧eme dφvat jako na p°φjmenφ,
kterΘ p°ipojφme k identifikßtoru a tφm zajistφme jeho jednoznaΦnost v rßmci
programu.
Syntax deklarace prostoru jmen je jednoduchß. Deklarace zaΦφnß
klφΦov²m slovem namespace, za nφm₧ nßsleduje identifikßtor prostoru
jmen. Pak nßsledujφ ve slo₧en²ch zßvorkßch deklarace slo₧ek prostoru jmen.
Nap°φklad takto:
// Deklarace prostoru jmen
namespace vstup {
int proud1;
void f();
class X;
}
Zde jsme v prostoru jmen vstup deklarovali globßlnφ prom∞nnou, funkci a t°φdu. PlnΘá jmΘno (tzv. kvalifikovanΘ jmΘno) tΘto prom∞nnΘ je vstup::proud1, plnΘ jmΘno funkce je vstup::f(), plnΘ jmΘno t°φdy je vstup::X. (╪φkßme, ₧e identifikßtory slo₧ek prostoru jmen kvalifikujeme jmΘnem prostoru jmen; k tomu pou₧φvßme operßtor ::.
Uvnit° prostoru jmen m∙₧eme deklarovat datovΘ typy, funkce, prom∞nnΘ, ale takΘ dalÜφ prostory jmen.
Funkci vstup::f(), stejn∞ jako t°φdu X m∙₧eme definovat
n∞kde dßle; v tom p°φpad∞ musφme jejich identifikßtory kvalifikovat jmΘnem
prostoru jmen.
void vstup::f(){
áááá // ...
}
class vstup::X{ /* ... */ };
Nic nßm ovÜem nebrßnφ zapsat jejich definice celΘ do prostoru jmen:
namespace vstup {
int proud1;
void f() { /* ... */ }
class X { /* ... */ };
}
Mimo prostor jmen vstup m∙₧eme v tΘm₧e programu deklarovat
funkci s prototypem
void f();
ánebo t°φdu X a nedojde ke konfliktu.
Slo₧ky prostoru jmen m∙₧eme mimo ôjejichö prostor jmen pou₧φvat,
pokud je kvalifikujeme jmΘnem prostoru jmen (nebo pokud je nezp°φstupnφme
pomocφ deklarace Φi direktivy using, o nich₧ si povφme dßle).
To znamenß, ₧e ve funkci g(), kterß nele₧φ v prostoru jmen
Vstup,
m∙₧eme napsat
void g()
{
áááá // Volßme funkci
f() z prostoru jmen vstup
áááá vstup::f();
áááá // Volßme funkci
f(), kterß nele₧φ v prostoru
áááá // jmen vstup
f();
áááá vstup::proud1 = 6589;
áááá // Deklarujeme instanci
t°φdy X
áááá vstup::X x;
}
Uvnit° prostoru jmen m∙₧eme jeho slo₧ky pou₧φvat bez kvalifikace.
To znamenß, ₧e v t∞le funkce vstup::f() m∙₧e vypadat nap°. takto:
void vstup::f()
{ááá // pou₧ije vstup::proud1
scanf("%d", &proud1);
X x;
}
P°itom je jedno, zda zapφÜeme definici funkce f()
uvnit° prostoru jmen nebo zda ji v prostoru jmen vstup pouze deklarujeme a definici
zapφÜeme n∞kde jinde. T∞lo funkce, deklarovanΘ v prostoru jmen, je v₧dy jeho
souΦßstφ.
Uvnit° prostoru jmen m∙₧eme deklarovat dalÜφ prostor jmen.
Nap°φklad takto:
namespace vnejsi{
namespace vnitrni{
void
h(){/* ... */}
}
void h(){
vnitrni::h();
}
}
JmΘno vno°enΘho prostoru jmen se sklßdß z identifikßtoru tohoto prostoru, ke kterΘmu p°ipojφme operßtorem :: jmΘno vn∞jÜφho prostoru jmen. Vno°en² prostor jmen v p°edchozφm p°φkladu se tedy jmenuje vnejsi::vnitrni.
Ve vn∞jÜφm prostoru jmen staΦφ jmΘna objekt∙, deklarovan²ch ve vno°enΘm prostoru jmen, kvalifikovat pouze jmΘnem vnit°nφho prostoru jmen. Nap°φklad funkci h(), deklarovanou v prostoru jmen vnejsi::vnitrni, m∙₧eme ve funkci vnejsi::h() volat zßpisem vnitrni::h().
Hloubka vno°ovßnφ prostor∙ jmen nenφ omezena.
Zavedenφ prostor∙ jmen znamenß mnoho psanφ navφc, a programßto°i
jsou, jak znßmo, lφnφ û ostatn∞ jako v∞tÜina lidφ. Proto nabφzφ C++ n∞kolik
mo₧nostφ, jak si uÜet°it prßci.
Mß-li prostor jmen dlouhΘ jmΘno, m∙₧e b²t jeho opakovanΘ vypisovßnφ nepohodlnΘ. Proto nabφzφ C++ mo₧nost prostor jmen p°ejmenovat û definovat pro n∞j alias. Nap°φklad prostor jmen vnejsi::vnitrni p°ejmenujeme na vv deklaracφ
namespace vv = vnejsi::vnitrni;
Potom budou zßpisy vnejsi::vnitrni::h() a vv::h() ekvivalentnφ.
Poznamenejme, ₧e alias m∙₧eme definovat pro jak²koli prostor
jmen, nejen pro vno°en². Alias umo₧≥uje takΘ pracovat s abstraktnφm prostorem
jmen: Jedinou deklaracφ aliasu urΦφme, o jak² prostor jmen jde. (Podobn∞ nap°.
deklarace typedef
umo₧≥uje pracovat s abstraktnφm datov²m typem.)
Pou₧φvßme-li Φasto jmΘna z n∞kterΘho prostoru jmen, m∙₧eme
p°ekladaΦi °φci, ₧e budeme kvalifikaci jmΘnem prostoru jmen vynechßvat. K
tomu slou₧φ tzv. direktiva using, je₧ mß tvar
using namespace jmΘno;
Za touto direktivou m∙₧eme pou₧φvat identifikßtor z prostoru jmen jmΘno bez kvalifikace.
Nap°φklad vÜechny identifikßtory ze standardnφ knihovny jazyka
C++ le₧φ v prostoru jmen std. To znamenß, ₧e napφÜeme-li
#include <iostream>
#include <list>
using namespace std;
m∙₧eme psßt
list<int> l;
for(int i = 0; i < 10; i++) l.push_front(i);
for(list<int>::iterator i = l.begin();
i != l.end(); i++)
{
cout << *i << endl;
}
Kdybychom vynechali direktivu using, museli bychom psßt std::list<int>, std::list<int>::iterator, std::cout a std::endl.
N∞kterΘ prostory jmen jsou znaΦn∞ rozsßhlΘ a my z nich pot°ebujeme
jen n∞kterß jmΘna; v takovΘm p°φpad∞ m∙₧e direktiva using
napßchat vφce Ükody ne₧ u₧itku. Proto nabφzφ jazyk C++ jeÜt∞ deklaraci using, je₧ umo₧≥uje specifikovat
jednotlivß jmΘna z prostoru jmen, kterß chceme pou₧φvat bez kvalifikace. Pokud
bychom cht∞li nap°. pou₧φvat bez kvalifikace jen identifikßtory z konce p°edchozφho
odstavce, mohli bychom napsat
using std::list;ááá //
list je jmΘno Üablony
using std::list<int>::iterator;
using std::cout;
using std::endl;
Podobn∞ budeme-li chtφt pou₧φvat bez kvalifikace jmΘno funkce
Vstup::f(),
napφÜeme
using vstup::f;áááá //
Jen jmΘno
V deklaraci using uvßdφme v₧dy jen jeden identifikßtor. Deklarace using std::list; umo₧≥uje pou₧φvat bez kvalifikace Üablonu list s jak²mikoli parametry.
Jednou ze zajφmav²ch vlastnostφ direktivy i deklarace using je, ₧e jsou tranzitivnφ. To znamenß: Uvedeme-li v prostoru jmen B direktivu
using namespace A;
a v prostoru jmen C direktivu
using namespace B;
m∙₧eme v prostoru jmen C pou₧φvat bez kvalifikace vÜechna jmΘna z prostoru jmen A. Podobn∞ zp°φstupnφme-li v prostoru jmen B n∞jakß jmΘna z prostoru jmen A deklaracφ using a uvedeme-li v prostoru jmen C direktivu
using namespace B;
budou v prostoru jmen C tato jmΘna p°φstupnß.
Pokud vßm to p°ipadß nesrozumitelnΘ, p°φklad to jist∞ vyjasnφ.
#include <iostream>
#include <list>
namespace pomocny {
ááááá using namespace std;
áááá // ... dalÜφ deklarace
}
namespace hlavni {
using namespace pomocny;
int Fufu()
{ááá áááááááá áááá áááá // To je OK -
list<int>
l;áá áááá
// tato jmΘna
cout
<< "ahoj"áá áááá // net°eba kvalifikovat
áááá << endl;
áááá áááá // ...á
}
}
V prostoru jmen pomocny jsme mj. uvedli direktivu
using
namespace std;, kterß v n∞m zp°φstupnila vÜechny identifikßtory z prostoru
jmen std.
V prostoru jmen hlavni jsme si zp°φstupnili vÜechna jmΘna z prostoru
jmen pomocny
direktivou using
namespace pomocny;, a proto je m∙₧eme v t∞le funkce hlavni::Fufu()
pou₧φt bez kvalifikace. Kdybychom zm∞nili deklaraci prostoru jmen pomocny
nßsledujφcφm zp∙sobem,
namespace pomocny {
using std::cout;
using std::endl;
using std::list;
using std::list<int>::iterator;
// ... a dalÜφ deklarace
}
mohli bychom dφky v prostoru jmen hlavni pou₧φvat bez
kvalifikace jen uvedenß jmΘna.
JmΘna, vnesenß do n∞jakΘho prostoru jmen pomocφ deklarace
nebo direktivy using, se chovajφ, jako kdyby byla jeho souΦßstφ. To
znamenß, ₧e platφ-li v²Üe uvedenß deklarace prostoru jmen pomocny,
m∙₧eme nap°. ve funkci main(), je₧ le₧φ mimo jak²koli prostor jmen, napsat
Pomocny::list<double> dl;
a bude to znamenat totΘ₧, jako kdybychom napsali
std::list<double> dl;
DalÜφ zajφmavou a u₧iteΦnou vlastnostφ prostor∙ jmen v jazyce
C++ je, ₧e je m∙₧eme deklarovat po Φßstech. NapφÜeme-li n∞kde v programu
namespace jupi {
áááá void fupi();
}
nic nßm nebrßnφ napsat n∞kde jinde
namespace jupi {
áááá void gupi();
}
a p°ekladaΦ si ob∞ Φßsti prostoru jmen jupi spojφ do jednoho celku. P°itom tyto Φßsti nemusφ le₧et v tΘm₧e souboru.
P°ekladaΦ ovÜem m∙₧e v₧dy pracovat jen se jmΘny, kterß u₧ byla deklarovßna, nedokß₧e se ôpodφvat dop°eduö. To znamenß, ₧e kdybychom napsali
namespace jupi {
áááá void fupi(){/* ...
*/}
}
void dupy(){
áááá jupi::gupi();áááá // Nelze
}
namespace jupi {
áááá void gupi(){/* ...
*/}
}
ohlßsil by p°ekladaΦ, ₧e pou₧φvßme funkci gupi(), kterß nenφ souΦßstφ prostoru jmen jupi.
P°φsluÜnost k prostoru jmen je t°eba pochopiteln∞ vyznaΦit
i v hlaviΦkov²ch souborech. V nich ovÜem zapisujeme pouze deklarace, nikoli
definice û jak je v C++ obvyklΘ.
Deklarujeme-li hlaviΦkov² soubor
// Souboru jupi.h
namespace jupi {
áááá void fupi();
áááá void gupi();
}
a vlo₧φme-li tento soubor direktivou
#include "jupi.g"
p°ijme p°ekladaΦ p°φklad z p°edchozφho oddφlu bez nßmitek.
SkuteΦnost, ₧e deklaraci prostoru jmen lze rozd∞lit na n∞kolik
Φßstφ, umo₧≥uje rozd∞lit velkΘ prostory jmen do n∞kolika soubor∙. Typick²m
p°φkladem je standardnφ knihovna jazyka C++, kterß le₧φ, jak vφme, v prostoru
jmen std.
Tato knihovna je popsßna v °ad∞ hlaviΦkov²ch soubor∙. SkuteΦnost, ₧e p°ekladaΦ
vidφ jen ta jmΘna, o nich₧ se dozvφ z deklaracφ, kterΘ si p°eΦte, umo₧≥uje
pracovat s menÜφ mno₧inou jmen, nikoli s cel²m prostorem jmen najednou.
NapφÜeme-li ve svΘm programu
#include <iostream>
bude p°ekladaΦ znßt jen jmΘna, deklarovanß v tomto hlaviΦkovΘm souboru iostream, nikoli vÜak jmΘna deklarovanß v hlaviΦkov²ch souborech list, queue, map a dalÜφch. (Pozor ovÜem na souΦßsti zp°φstupn∞nΘ dφky tranzitivit∞ direktiv a deklaracφ using. Jazyk C++ bohu₧el nespecifikuje vzßjemnΘ zßvislosti hlaviΦkov²ch soubor∙.)
Poznamenejme, ₧e v tomto ohledu se v²razn∞ liÜφ pojetφ prostor∙
jmen v C++ od pojetφ prostor∙ jmen v jazyce C#.
V deklaraci prostoru jmen nemusφme uvΘst jeho jmΘno; pak
dostaneme tzv. anonymnφ (nepojmenovan²) prostor jmen. M∙₧eme nap°. napsat
namespace {
áááá void huhu() {/* ...
*/ }
áááá int x;
}
Na identifikßtory deklarovanΘ v anonymnφm prostoru jmen se
m∙₧eme odvolßvat prost°ednictvφm samotnΘho identifikßtoru, p°φpadn∞ identifikßtoru,
p°ed kter² p°ipojφme unßrnφ operßtor :: û ale pouze v
rßmci samostatn∞ p°eklßdanΘ Φßsti programu, v n∞m₧ je tento prostor jmen deklarovßn.
P°ekladaΦ toti₧ spojφ vÜechny anonymnφ prostoru jmen v jednom samostatn∞ p°eklßdanΘm modulu v jeden prostor jmen a tomu p°id∞jφ jak²si vnit°nφ identifikßtor. V d∙sledku toho identifikßtory, deklarovanΘ v tomto prostoru jmen, nejsou vid∞t mimo dan² modul. Prom∞nnΘ a funkce, deklarovanΘ v anonymnφm prostoru jmen, se tedy vlastn∞ chovajφ jako statickΘ (deklarovanΘ s modifikßtorem static).
Podφvejme se na jednu z mnoha variant proslulΘho programu öHello, worldö:
// Hello, world û po kolikßtΘ u₧
include <iostream>
using std::cout;
using std::endl;
int main()
{
ááááááá cout <<
"Ahoj, lidi" << endl;
ááááááá return 0;
}
O objektech cout a endl jsme p°ekladaΦi °ekli, ₧e pat°φ do prostoru jmen std. Neuvedli jsme ale deklaraci
using std::operator<<;
kterß by zp°φstupnila p°etφ₧enΘ operßtory <<.
P°esto p°ekladaΦe, kterΘ odpovφdajφ souΦasnΘmu standardu jazyka C++, tento
program bez problΘm∙ p°elo₧φ û operßtory << najdou a pou₧ijφ. Za to
vd∞Φφme pravidlu, kterΘ °φkß, ₧e operßtory a funkce se vyhledßvajφ nejen v
kontextu jejich pou₧itφ (tj. v prostoru jmen, v n∞m₧ jsou volßny), ale i v
prostorech jmen sv²ch operand∙, resp. parametr∙. (Toto pravidlo se obvykle
oznaΦuje jako Koenigovo vyhledßvßnφ.)
Operßtory << v tomto p°φkladu jsou sice pou₧ity mimo jak²koli
prostor jmen, ale jejich operandy û cout a endl û le₧φ v prostoru
jmen std,
a proto je bude p°ekladaΦ hledat i tam, a tam je takΘ najde.
Podφvejme se jeÜt∞ na jeden p°φklad:
namespace prvni
{
áclass X{};
ávoid f(X x){}
}
int main()
{
á Prvni::X xx;
á f(xx); áááááááá áááá // Ok
á return 0;
}
TakΘ zde pou₧ije p°ekladaΦ vyhledßvßnφ zßvislΘ na parametrech.
Funkci f()najde bez problΘm∙, i kdy₧ jsme jejφ jmΘno nekvalifikovali
jmΘnem prostoru jmen prvni, nebo¥ ji bude hledat nejen
mimo prostory jmen, ale i v prostoru jmen prvni, v n∞m₧ je deklarovßn typ
X
skuteΦnΘho parametru.
K v²znamu Koenigova vyhledßvßnφ se vrßtφme p°φÜt∞.
Jazyk C++ p°evzal standardnφ knihovny jazyka C, a tedy takΘ hlaviΦkovΘ soubory, v nich₧ jsou makra, funkce, typy a konstanty z tΘto knihovny deklarovßny. Nßzvy t∞chto soubor∙ se ovÜem v C++ zm∞nily: Odpadla p°φpona .h a p°ed jmΘno se p°ipojil znak c. To znamenß, ₧eá nap°. hlaviΦkov² soubor, znßm² v jazyce C pod nßzvem stdio.h, se v C++ jmenuje cstdio.
Chceme-li n∞kterou z konstrukcφ z knihovny jazyka C knihovny
pou₧φt v C++, mßme dv∞ mo₧nosti:
- M∙₧eme pou₧φt jmΘno hlaviΦkovΘho souboru podle pravidel jazyka C++. V tom p°φpad∞ budou identifikßtory z n∞j le₧et v prostoru jmen std.
- M∙₧eme pou₧φt hlaviΦkov² soubor z jazyka C tak, jak jsme byli v C zvyklφ. V tom p°φpad∞ budeme identifikßtory z tohoto hlaviΦkovΘho souboru pou₧φvat bez kvalifikace. (Tuto mo₧nost standard p°ipouÜtφ, ale poklßdß ji za zastaralou.)
To znamenß, ₧e napφÜeme-li
#include <cstdio>
musφme psßt
std::printf("Ahoj, lidi");
nebo pou₧φt direktivu Φi deklaraci using. Na druhΘ stran∞
napφÜeme-li
#include <stdio.h>
m∙₧eme napsat
printf("Ahoj, lidi");
HlaviΦkovΘ soubory jazyka C++ podle standardu nemajφ p°φponu .h. NicmΘn∞ ve starÜφch verzφch jazyka, kterΘ neobsahovaly prostory jmen, tyto p°φponu m∞ly, a proto °ada p°ekladaΦ∙ pro n∞ pou₧φvß podobnou konvenci jako pro hlaviΦkovΘ soubory z jazyka C: Uvedeme-li v jejich jmΘnu p°φponu .h, nemusφme jmΘna z nich kvalifikovat jmΘnem prostoru jmen std.
V ·vodu jsme si °ekli, ₧e prostory jmen jednφm z poslednφch velk²ch rys∙ jazyka C++, kter² p°ekladaΦe implementovaly. Dopl≥me, ₧e mnohΘ s nimi majφ problΘmy dodnes. Nap°φklad Visual C++ .NET implementuje Koenigovo vyhledßvßnφ pouze pro operßtory volanΘ operßtorov²m zßpisem. Pro funkce, a dokonce ani pro operßtory volanΘ funkΦnφm zßpisem, je nepou₧φvß.
Tolik o prostorech jmen a o Koenigov∞ vyhledßvßnφ. P°φÜt∞ se podφvßme na d∙sledky tΘto konstrukce pro prßci s t°φdami v C++.
1. B. Stroustrup, M. A. Ellis: The Annotated C++ Reference Manual. Addison-Wesley 1991.