Krok ke standardu


V Φlßnku Visual C++, tentokrßt vizußlnφ v tomto Φφsle Chipu jsem se zmφnil, ₧e implementace jazyka C++ ve Visual C++ .NET verze 2003 (VC03) se ve srovnßnφ s p°edchozφ verzφ z roku 2002 (VC02) v²znamn∞ p°iblφ₧ila po₧adavk∙m standardu [1]. V tomto Φlßnku se podφvßme na zm∞ny ve srovnßnφ s p°edchozφ verzφ a na to, kde nesoulad z∙stal.
Je t°eba upozornit, ₧e jde o zm∞ny, kterΘ se b∞₧n²ch program∙ zpravidla p°φliÜ nedotknou, nicmΘn∞ v programech, kterΘ vyu₧φvajφ ähraniΦnφô vlastnosti p°ekladaΦe (nebo standardu jazyka), mohou hrßt roli.

Jazyk C

Implementace jazyka C ve Visual C++ .NET 2003 vychßzφ ze starÜφ verze standardu [2] z roku 1990. To znamenß, ₧e v∞tÜina novinek standardu [3] z roku 1999 zde chybφ. PodrobnΘmu popisu novinek standardu [3] jsme se v∞novali v Φlßncφch [4], [5] a [6], a proto se zde omezφme na struΦn² v²Φet. KonkrΘtn∞ jde p°edevÜφm o:

ò deklaraci restringovan²ch ukazatel∙ (klφΦovΘ slovo restrict);
ò umφst∞nφ deklarace kdekoli mezi p°φkazy;
ò deklaraci lokßlnφch automatick²ch polφ s hornφ mezφ zadanou nekonstantnφm v²razem;
ò deklarace polφ s klφΦov²mi slovy restrict nebo static v indexu jako parametr∙ funkcφ (äkvalifikßtory typuô v indexu);
ò nemo₧nost pou₧φvat univerzßlnφ jmΘna znak∙ v identifikßtorech;
ò datovΘ typy pro reprezentaci komplexnφch Φφsel;
ò hlaviΦkov² soubor stdint.h obsahujφcφ typedefy pro alternativnφ oznaΦenφ cel²ch Φφsel a pro rozÜi°ujφcφ celoΦφselnΘ typy;
ò p°φstup k v²poΦetnφmu prost°edφ pro reßlnß Φφsla;
ò pou₧φvßnφ pojmenovan²ch inicializßtor∙ slo₧ek polφ a struktur;
ò vytvß°enφ literßl∙ typ∙ polφ nebo slo₧ek;
ò identifikßtor __func__ v t∞le funkce atd.

K dispozici jsou (64bitovΘ) celoΦφselnΘ typy long long a unsigned long long. Jde o alternativnφ oznaΦenφ pro starΘ znßmΘ typy __int64 a unsigned __int64, kterΘ lze samoz°ejm∞ pou₧φvat i nadßle. (Poznamenejme ale, ₧e typy __int64 a unsigned __int64 p°edstavovaly rozÜφ°enφ, zatφmco typy long long a unsigned long long jsou souΦßstφ novΘho standardu jazyka C.)
Dßle je ve Visual C++ .NET k dispozici klφΦovΘ slovo _inline (mφsto nov²m standardem jazyka C zavedenΘho inline), kterΘ û podobn∞ jako v C++ û vyjad°uje po₧adavek, aby funkce byla p°elo₧ena tak, aby b∞₧ela co nejrychleji. Poznamenejme, ₧e standard ne°φkß, jak toho dosßhnout. M∙₧e to b²t tak, ₧e se bude na mφsto volßnφ vklßdat t∞lo volanΘ funkce, m∙₧e se ale pou₧φt i jin² trik û to je ponechßno na implementaci.

K dispozici je takΘ komentß° zaΦφnajφcφ dv∞ma lomφtky (po vzoru C++).

Jazyk C++

Jazyk C++ je podstatn∞ rozsßhlejÜφ a odchylky od standardu stejn∞ jako zm∞ny v n∞m jsou v∞tÜinou pom∞rn∞ nenßpadnΘ, tak₧e se jim budeme v∞novat podrobn∞ji. Poznamenejme, ₧e jako podklad k tomuto Φlßnku mi poslou₧il internetov² materißl [4] v∞novan² p°edchozφ verzi, tedy Visual C++ .NET verze 2002.

Alternativnφ reprezentace operßtor∙
Vzhledem k tomu, ₧e nßrodnφ klßvesnice v mnoha jazycφch neobsahujφ znaky &, ^, | a dalÜφ, po₧aduje standard (v oddφlu 2.11), aby implementace nabφzely alternativnφ reprezentace operßtor∙ &, ^, | , &=, ^=, |= a n∞kter²ch dalÜφch klφΦov²mi slovy and, xor, or, and_eq atd.
VC03 tato klφΦovß slova neobsahuje. Pokud je chceme pou₧φvat, musφme vyu₧φt preprocesoru a maker definovan²ch v hlaviΦkovΘm souboru <iso646.h>.

Koenigovo vyhledßvßnφ
Oficißlnφ nßzev tohoto nßstroje znφ vyhledßvßnφ jmen v zßvislosti na parametrech (argument-dependent name lookup) a je popsßn ve standardu v oddφlu 3.4.2. Jde o to, ₧e p°i volßnφ funkce nebo operßtoru se jeho deklarace hledß nejen v aktußlnφm prostoru jmen, ale i v prostoru jmen, v n∞m₧ jsou definovßny typy parametr∙. Nap°φklad nßsledujφcφ program je podle standardu sprßvn² a

namespace A

{
struct S{};
void f(S){}
}

void main()
{
A::S s;
f(s);
}

zavolß se v n∞m funkce A::f(), i kdy₧ nenφ explicitn∞ kvalifikovßna a prostor jmen A jsme ve funkci main() nijak nezp°φstupnili.
Ve VC02 fungovalo Koenigovo vyhledßvßnφ pouze pro p°etφ₧enΘ operßtory, nikoli vÜak pro obyΦejnΘ funkce. P°edchozφ program tedy p°ekladaΦ odmφtl; ve VC03 funguje Koenigovo vyhledßvßnφ u₧ podle po₧adavk∙ ve standardu a uveden² program se p°elo₧φ bez problΘm∙.
Obor prom∞nnΘ deklarovanΘ v podmφnce p°φkazu if
Nejprve si p°ipome≥me, ₧e standard jazyka C++ dovoluje deklarovat v podmφnce p°φkazu if prom∞nnou a inicializovat ji; hodnotou podmφnky je pak hodnota p°i°azenß tΘto prom∞nnΘ. Jestli₧e platφ nap°. deklarace

int f() { // N∞jakß funkce vracejφcφ int
return 1;
}

lze napsat

if(int n = f())
NecoDelej(n);
else
NedelejNic(n);

Prom∞nnou n lze pou₧φvat v obou v∞tvφch p°φkazu if. Standard (oddφl 6.4) ale nedovoluje v blocφch p°φkazu if deklarovat novou prom∞nnou se stejn²m jmΘnem. NapφÜeme-li

if(int n = f()) {
int n = 21; // Chyba - nelze
NecoDelej(n);
} else {
int n = 78;
NedelejNic(n); // Chyba - nelze
}

m∞l by p°ekladaΦ tyto deklarace oznaΦit za chybnΘ. P°edchozφ verze, VC02, takovΘto deklarace tolerovala; souΦasnß verze zde sprßvn∞ ohlßsφ chybu.
Poznamenejme, ₧e v blocφch vno°en²ch do t∞chto dvou blok∙ dovoluje standard identifikßtor n pou₧φt k jin²m ·Φel∙m; omezenφ se t²kß pouze blok∙ na änejvyÜÜφ ·rovniô, tedy na ·rovni p°φkazu_1 a p°φkazu_2 v syntaktickΘm popisu p°φkazu if.
KlφΦovΘ slovo export u Üablon
Standard v kap. 14 p°edpoklßdß, ₧e v deklaraci Üablony bychom m∞li mφt mo₧nost pou₧φt klφΦovΘ slovo export, nap°.

export template <typename T> void f(T);

nebo

export template <typename T> class X;

a definici Üablony zapsat nap°. v jinΘm souboru. Od zavedenφ tohoto klφΦovΘho slova si °ada lidφ slibovala mo₧nost utajit zdrojov² k≤d Üablonov²ch knihoven, nebo¥ m∞lo umo₧nit zapisovat do hlaviΦkov²ch soubor∙ pouze deklarace v uvedenΘm tvaru.
VC03 ho nepodporuje, stejn∞ jako p°edchozφ verze VC02. D∙vod je prost² û jde o jednu z nejproblematiΦt∞jÜφch konstrukcφ ve standardu a p°edpoklßdß se, ₧e novß verze standardu jazyka C++ ji odstranφ. V souΦasnΘ dob∞ ji, pokud vφm, implementuje jedin² p°ekladaΦ na sv∞t∞ a ukazuje se, ₧e vede k poruÜenφ n∞kter²ch pravidel zßkladnφ filozofie C++ û nap°. v podstat∞ znemo₧≥uje odd∞len² p°eklad r∙zn²ch Φßstφ programu.

Operßtor new
Podle standardu (oddφl 3.7.3) operßtor new p°i ne·sp∞ÜnΘ alokaci pam∞ti vyvolß v²jimku typu std::bad_alloc. V p°edchozφ verzi VC02 vracel v p°φpad∞ ne·sp∞chu hodnotu 0, v souΦasnΘ verzi se ji₧ chovß podle standardu.
StarΘ chovßnφ (vracenφ nuly) lze nastavit p°epφnaΦem p°ekladaΦe.
Vedle toho lze podle standardu (18.4) nastavit chovßnφ operßtoru new p°i ne·sp∞ÜnΘ alokaci pomocφ standardnφ funkce set_new_handler(). Tato funkce oΦekßvß jako parametr ukazatel na funkci typu void bez parametr∙. Volßnφm set_new_handler() definujeme funkci, kterß se mß volat v p°φpad∞ ne·sp∞ÜnΘ alokace; tato funkce m∙₧e bu∩ ukonΦit program, nebo se pokusit zφskat volnou pam∞¥ uvoln∞nφm nepot°ebn²ch objekt∙. (Jde tedy o jakousi variantu garbage collectoru.) Po zavolßnφ tΘto funkce se operßtor new pokusφ o novou alokaci.
Ve VC03, stejn∞ jako ve VC02, je mφsto funkce set_new_handler() k dispozici funkce _set_new_handler(), kterß oΦekßvß ukazatel na funkci s parametrem typu size_t a vracejφcφ hodnotu typu int. Parametr tohoto handlenu vyjad°uje velikost pot°ebnΘ pam∞ti a vracenß hodnota vyjad°uje, zda se poda°ilo pam∞¥ zφskat (0 znamenß ne·sp∞ch).

╪et∞zcovΘ konstanty
P°edchozφ verze, VC02, dovolovala m∞nit °et∞zcovΘ konstanty za b∞hu programu. Ve VC03 jsou tyto konstanty souladu se standardem (odst. 2.13.4) typu const char[] a jsou ulo₧eny v pam∞ti urΦenΘ pouze pro Φtenφ. Pokus o zm∞nu °et∞zcovΘ konstanty vyvolß b∞hovou chybu.

P°et∞₧ovßnφ Üablon funkcφ obyΦejn²mi funkcemi
èablony funkcφ lze p°et∞₧ovat obyΦejn²mi funkcemi se stejn²m jmΘnem. Jestli₧e lze v programu pou₧φt obyΦejnou funkci nebo funkci vytvo°enou podle Üablony, mß podle standardu (14.5) p°ednost obyΦejnß funkce.
VC03 ji₧ toto pravidlo respektuje, tak₧e nap°. program

#include <iostream>
using namespace std;

template<typename T>
void f(T)
{
cout << "Ve funkci void f(T)" << endl;
}

void f(int)
{
cout << "Ve funkci void f(int)" << endl;
}

int main()
{
f(4);
return 0;
}

zavolß nynφ neÜablonovou verzi a vypφÜe

Ve funkci void f(int)

Inicializace POD a skalßrnφch typ∙

Standard jazyka dovoluje inicializovat tzv. POD-struktury a prom∞nnΘ skalßrnφch typ∙ zßpisem podobn²m volßnφ konstruktoru.
Nap°φklad takto:

struct bod // POD-struktura
{
int i; double j;
};

void main()
{
bod b = bod();
}

Podobn∞ m∙₧eme napsat

int m = int();

Tyto (mo₧nß na pohled podivnΘ) konstrukce byly do jazyka C++ zavedeny kv∙li Üablonßm, aby bylo mo₧nΘ pou₧φvat skalßrnφ typy, POD-struktury a objektovΘ typy stejn²m zp∙sobem.
VC02 tento zp∙sob inicializace nepodporoval, VC03 ji₧ ano.
Poznamenejme, ₧e pod oznaΦenφm POS-struktury se skr²vajφ struktury ve smyslu jazyka C, tj. se vÜemi slo₧kami ve°ejn∞ p°φstupn²mi, bez virtußlnφch metod a bez konstruktor∙. OznaΦenφ POD je zkratkou anglick²ch slov plain old data (n∞co jako äobyΦejnß starß dataô).

Deklarace ukazatele na funkci
VC02 dovoloval uvΘst v deklaraci ukazatele na funkci specifikaci implicitnφch hodnot parametr∙. VC03 to v souladu se standardem nedovoluje.

Vno°enΘ Üablony
èablona m∙₧e b²t souΦßstφ t°φdy nebo Üablony t°φdy; pak hovo°φme o vno°enΘ Üablon∞ (standard 14.5.2). Standard p°itom dovoluje uvΘst v deklaraci Üablony t°φdy pouze deklaraci, nikoli definici vno°enΘ Üablony metody. Nap°φklad takto:

template <typename T>
class Alfa
{
public:
// Deklarace, nikoli definice
template<typename U> int comp(const U&);
// Definice
template<typename U> Alfa(int x):i(x){}
private:
int x;
};

template<typename T>
template<typename U>
int Alfa<T>::comp(const U& u)
{
return true;
}

void main()
{ // N∞jakΘ pou₧itφ
}

Zde Üablona t°φdy Alfa obsahuje vno°enΘ Üablony konstruktoru a metody comp(). P°itom Üablona konstruktoru je uvnit° Üablony t°φdy Alfa definovßna, kde₧to Üablona metody comp() je pouze deklarovßna a je definovßna dßle.
VC02 dovoloval pouze uvΘst celou definici uvnit°, tj. deklaraci Üablony metody comp() oznaΦoval za chybnou. VC03 ji₧ tuto situaci zvlßdne.

Parcißlnφ specializace Üablon
Parcißlnφ specializace Üablon (standard 14.5.4) p°edstavuje zp∙sob, jak definovat zvlßÜtnφ p°φpady Üablon objektov²ch typ∙ pro urΦitΘ typy nebo skupiny typ∙ v p°φpad∞ typov²ch parametr∙ nebo pro urΦitΘ konkrΘtnφ hodnoty netypov²ch parametr∙. Podφvejme se na p°φklad:

// Primßrnφ Üablona (nejobecn∞jÜφ definice)
template <typename T, int i>
struct A
{
T f();
};

template<typename T, int i> T A<T,i>::f()
{ return i;
}

// Parcißlnφ specializace pro jakΘkoli T
// a pro hodnotu 2
template<typename T>
struct A<T, 2>
{
T f(){return 555;}
};


void main()
{
A<double, 2> x; // Zde se pou₧ije specializovanß Üablona
double d = x.f();
}

V deklaraci prom∞nnΘ x ve funkci main() se pou₧ije druhß verze Üablony, nebo¥ druh² parametr Üablony mß hodnotu 2, a pro tu je k dispozici parcißlnφ specializace.
VC02 parcißlnφ specializaci Üablon nepodporoval, VC03 ji podporuje.

Parcißlnφ uspo°ßdßnφ Üablon funkcφ
Standard sice nedefinuje parcißlnφ specializaci Üablon funkcφ, umo₧≥uje vÜak jejich p°et∞₧ovßnφ a tzv. parcißlnφ °azenφ: Lze-li k vytvo°enφ instance Üablony pou₧φt n∞kolika Üablon, pou₧ije se ta, kterß nejlΘpe odpovφdß skuteΦn²m parametr∙m (standard 14.5.5.2). Podφvejme se na p°φklad:

// T°i r∙znΘ Üablony funkce
template<class T> void f(T a){}
template<class T> void f(T* a){}
template<class T> void f(const T* a){}

void main()
{
const int a = 10;
const int *p = &a;
f(p);
}

Zde se pou₧ije funkce vytvo°enß podle t°etφ Üablony, nebo¥ typ skuteΦnΘho parametru odpovφdß nejblφ₧e typu parametru prßv∞ tΘto Üablony.
VC02 parcißlnφ uspo°ßdßnφ Üablon funkcφ nepodporuje, VC03 ano.

V²jimky
V deklaraci funkce lze specifikovat v²jimky, kterΘ se mohou z tΘto funkce rozÜφ°it. K tomu slou₧φ klφΦovΘ slovo throw, uvedenΘ za prototypem a nßsledovanΘ seznamem typ∙ dovolen²ch v²jimek uzav°en²m v zßvorkßch. PrßzdnΘ zßvorky znamenajφ, ₧e se z danΘ funkce nesmφ rozÜφ°it ₧ßdnß v²jimka. Pokud se z funkce rozÜφ°φ v²jimka nedovolenΘho typu, program zavolß funkci unexpected_exception(), kterß typicky okam₧it∞ ukonΦφ program.
VC03, podobn∞ jako ve VC02, specifikaci v²jimek v deklaraci funkce nepodporuje. P°esn∞ji: dovoluje ji pou₧φt, nicmΘn∞ typ v²jimky, kterß se z funkce rozÜφ°φ, nekontroluje. V²znam mß pouze specifikace throw(), kterß °φkß, ₧e se z funkce nesmφ rozÜφ°it ₧ßdnß v²jimka, a specifikace throw(...), kterß °φkß, ₧e se z funkce smφ rozÜφ°it jakßkoli v²jimka. Specifikace throw(typ) je ekvivalentnφ specifikaci throw(...).
Zde tedy nedoÜlo ve srovnßnφ s p°edchozφ verzφ k ₧ßdnΘ zm∞n∞.

Funkce uncaught_exception()
Funkce uncaught_exception() umo₧≥uje zjistit, zda je v dob∞ jejφho volßnφ aktivnφ n∞jakß v²jimka. I kdy₧ to vypadß podivn∞, je to d∙le₧itΘ nap°. v destruktorech objektov²ch typ∙. P°i vzniku v²jimky se, jak znßmo, volajφ destruktory lokßlnφch automatick²ch instancφ objektov²ch typ∙ a z nich se v tomto p°φpad∞ nesmφ rozÜφ°it dalÜφ v²jimka. Pro zjiÜt∞nφ, zda je destruktor volßn p°i v²jimce, nebo za normßlnφ situace, se hodφ prßv∞ tato funkce. Podφvejme se na schematick² p°φklad:

#include <exception>
#include <iostream>
#include <string>

class Test
{
public:
Test( std::string msg ) : m_msg( msg )
{
std::cout << "In Test::Test(\"" << m_msg << "\")" << std::endl;
}
~Test( )
{
std::cout << "In Test::~Test(\"" << m_msg << "\")" << std::endl
<< " std::uncaught_exception( ) = "
<< std::uncaught_exception( )
<< std::endl;
}
private:
std::string m_msg;
};

// uncaught_exception vrßtφ v destruktoru true
// pro objekt vytvo°en² uvnit° bloku try, nebo¥
// destruktor je volßn jako souΦßst ·klidu zßsobnφku

int main( void )
{
Test t1( "mimo blok try " );
try
{
Test t2( "uvnit° bloku try " );
throw 1;
}
catch (int i) {
}
}

Tento program vypφÜe:

In Test::Test("mimo blok try ")
In Test::Test("uvnit° bloku try ")
In Test::~Test("uvnit° bloku try ")
std::uncaught_exception( ) = 1

VC02 a starÜφ verze Visual C++ funkci uncaught_exception() sice implementovaly, ale vracela za vÜech okolnostφ false. Ve VC03 ji₧ funguje.

Prostor jmen stdext
Standardnφ knihovna jazyka C++ podle souΦasnΘ normy neobsahuje heÜovou tabulku ani ₧ßdn² podobn² nßstroj. D∙vod je prost² û standardizaΦnφ komise je prost∞ nestihla navrhnout a schvßlit. V d∙sledku toho nabφzejφ r∙znΘ implementace tΘto knihovny vlastnφ verze heÜovacφch kontejner∙. Ve VC02 a VC03 to jsou Üablony hash_map<>, hash_multimap<>, hash_set<> a hash_multiset<>. (PodobnΘ je to ostatn∞ i v jin²ch implementacφch.) Ve VC02 byla tato nestandardnφ rozÜφ°enφ umφst∞na v prostoru jmen std spolu se standardnφmi souΦßstmi knihovny; ve VC03 jsou p°emφst∞na do prostoru jmen stdext. Pokud je pou₧ijeme äpostaruô, s prostorem jmen std, p°ekladaΦ vypφÜe varovßnφ. (Lze si ovÜem p°edepsat, ₧e mß hlßsit chybu.)

Na zßv∞r
Je z°ejmΘ, ₧e implementace C++ ve Visual C++ .NET prod∞lala v²znamnΘ zm∞ny k lepÜφmu; i kdy₧ se neshoduje pln∞ se standardem, °ada mφst, kde se od standardu odchylovala p°edchozφ verze, byla opravena. Na druhΘ stran∞ implementace jazyka C se prakticky nezm∞nila, stßle jde o jazyk C podle normy ze zaΦßtku devadesßt²ch let s °adou rozÜφ°enφ.


Odkazy
1. International standard ISO/IEC 14882 û 1998. Programming languages û C++.
2. International standard ISO/IEC 9899 û 1990. Programming languages û C.
3. International standard ISO/IEC 9899 û 1999. Programming languages û C.
4. Compiler Limits. http://msdn.microsoft.com/library/
5. M. Virius: CΘΦko na p°elomu tisφciletφ (1). Chip 9/2002, str. 130.
6. M. Virius: CΘΦko na p°elomu tisφciletφ (2). Chip 10/2002, str. 154.
7. M. Virius: CΘΦko na p°elomu tisφciletφ (3). Chip 11/2002, str. 160.

Miroslav Virius