MyÜlenka prostor∙ jmen (namespace) je v C++ pom∞rn∞ starß; ovÜem v pr∙b∞hu standardizace tohoto jazyka proÜla n∞kolika prom∞nami.
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 dost pozd∞, a₧ v polovin∞ devadesßt²ch let. 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.
P°φliÜ mnoho identifikßtor∙
K Φemu jsou prostory jmen dobrΘ? ZaΦneme zeÜiroka, od poΦφtaΦovΘ prehistorie.
ProblΘmy se jmΘny
Identifikßtory prvnφch verzφ 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 k dispozici 26 jednoznakov²ch identifikßtor∙, 26 x 36 dvouznakov²ch atd. a₧ 26 x 365 Üestiznakov²ch identifikßtor∙. To je vφce ne₧ 16 miliard r∙zn²ch jmen, a to se autor∙m jazyka FORTRAN 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 umo₧nily pou₧φvat podstatn∞ delÜφ identifikßtory.
S r∙stem rozsahu projekt∙ a takΘ s r∙stem rozsahu programov²ch knihoven se vÜak 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∞.
╪eÜenφ v C
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.
Deklarace prostoru jmen
Syntaxe 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.
Definice slo₧ek
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 ni₧ by doÜlo ke konfliktu.
Pou₧itφ slo₧ek prostoru jmen
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φ.
Vno°enΘ prostory jmen
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.
ProΦ tolik psßt
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.
Alias prostoru jmen
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.)
Direktiva using
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.
Deklarace using
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.
using je tranzitivnφ
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 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;
Deklarace po Φßstech
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.
HlaviΦkovΘ soubory
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.
Knihovny
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#.
Anonymnφ prostory jmen
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φ prostory 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).
Vyhledßvßnφ podle parametr∙
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∞.
C++ a knihovny jazyka C
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 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.
Implementace
V ·vodu jsme si °ekli, ₧e prostory jmen jsou 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ß.
P°φÜt∞
Tolik o prostorech jmen a o Koenigov∞ vyhledßvßnφ. P°φÜtφ pokraΦovßnφ, v n∞m₧ se podφvßme na d∙sledky tΘto konstrukce pro prßci s t°φdami v C++, najdete nikoli v tiÜt∞nΘm Φasopise, ale na redakΦnφm Chip CD. Pro mnohΘ z vßs to bude mφt v²hodu snadn∞jÜφ prßce s textem a s v²pisy program∙.
Miroslav Virius, autor@chip.cz
Odkazy: B. Stroustrup, M. A. Ellis: The Annotated C++ Reference Manual. Addison-Wesley 1991. International Standard ISO/IEC 14882-1998. Programming Languages - C++.