Kurz C++ (19.)

Vφtejte u dalÜφho dφlu, kter² bude v∞novßn prostor∙m jmen a specißlnφm operßtor∙m, kterΘ nßm mohou pomoci p°i p°etypovßnφ.

19.1. Prostory jmen

    Minule jsme si psali o v²jimkßch, kde jsme se seznßmili s prostory jmen. KonkrΘtn∞ s prostorem jmen std, do n∞ho₧ spadajφ funkce ze standardnφ knihovny jazyka C++. Prostory jmen umo₧≥ujφ rozd∞lit identifikßtory, tedy nap°. prom∞nnΘ nebo funkce, do velk²ch skupin. Tyto skupiny jsou "odstφn∞nΘ", tedy v jednom prostoru jmen m∙₧e b²t identifikßtor a, kter² p°edstavuje nap°. prom∞nnou,  v jinΘm pak identifikßtor a, kter² m∙₧e p°edstavovat funkci. Za chvφli uvidφte, ₧e k identifikßtor∙m definovan²m v prostoru jmen p°istupujeme podobn∞, jako jsme p°istupovali k statick²m prom∞nn²m definovan²m uvnit° t°φdy.

    Prostor jmen deklarujeme nßsledujφcφm zp∙sobem:

namespace JmenoProstoru { Deklarace_prvku_prostoru }

19.1.1. PojmenovanΘ prostory jmen

    JmenoProstoru nahradφme zvolen²m nßzvem. M∙₧eme se vÜak rozhodnout tento identifikßtor vynechat, pak dostaneme tzv. anonymnφ prostor jmen. Deklarace_prvku_prostoru jsou pak klasickΘ deklarace funkcφ a prom∞nn²ch po vzoru globßlnφch prom∞nn²ch. Deklarace prostoru jmen m∙₧e b²t rozd∞lena i do vφce Φßstφ (Φßsti dokonce nemusφ le₧et pouze v jednom souboru):

namespace Test { int i; }

namespace Test { float f; }

    Nynφ ob∞ prom∞nnΘ le₧φ ve stejnΘm prostoru jmen s nßzvem Test. Pokud definujeme dva prostory jmen, kterΘ jsou vzßjemn∞ provßzßny, pak musφme dodr₧et pravidla, se kter²mi jsme se setkali u₧ u t°φd. M∞jme prostory jmen Test1 a Test2 podle nßsledujφcφho k≤du:

namespace Test1
{
    int i;
    Test1() { Test2::f = 5.0f; }
}

namespace Test2 { float f; }

    P°i pokusu o p°eklad nßm p°ekladaΦ ohlßsφ chybu, proto₧e neznß ani prostor jmen Test2 a u₧ v∙bec ne prom∞nnou f. Tento problΘm se dß vy°eÜit prohozenφm obou deklaracφ. Na tomto ·seku k≤du takΘ vidφme, jak se p°istupuje k prvk∙m uvnit° prostoru jmen - pomocφ operßtoru Φty°teΦky ::. Za chvφli si ukß₧eme jeÜt∞ jin² zp∙sob, kter² nßm uÜet°φ velkΘ mno₧stvφ napsanΘho k≤du.

    Pokud bychom zapisovali t∞la funkcφ p°φmo do deklarace prostoru jmen, deklarace by se velmi brzo stala nep°ehlednou. Proto podobn∞ jako u t°φd lze t∞la funkcφ umφstit mimo:

namespace Matika
{
    int chyba;   
// Nastala naposledy chyba ?
    int NaDruhou(int a);
}

int Matika::NaDruhou(int a)
{
    chyba = 0;
// Nenastala chyba
    return a*a;
}

    Prom∞nnß chyba ve funkci Odmocni() odpovφdß prom∞nnΘ chyba z prostoru jmen Matika. Pokud by existovala globßlnφ prom∞nnß s nßzvem chyba, pak je zastφn∞na v t∞le funkcφ nßle₧ejφcφch do tohoto prostoru prom∞nnou z prostoru jmen Matika. Pokud bychom cht∞li pou₧φt z n∞jakΘ funkce takovouto globßlnφ prom∞nnou, pak musφme pou₧φt operßtoru Φty°teΦka ::, tedy nap°. ::chyba.

    Nynφ si povφme o druhΘm zp∙sobu, jak si zp°φstupnit identifikßtory n∞jakΘho prostoru jmen. Ukß₧eme si to na rozsßhlΘm prostoru std. Zkusφme si nßsledujφcφ p°φklad:

#include <iostream> // tady opravdu neni .h

int main(int argc, char* argv[])
{
    std::cout << "Nazdar!!!" << std::endl;
    return 0;
}

    Podle standardu jazyka C++ bychom m∞li hlaviΦkovΘ soubory C++ psßt bez p°φpony .h (stdio.h, conio.h a dalÜφ soubory jazyka C z∙stßvajφ s p°φponou). Potom vÜak musφme vÜechny identifikßtory uvßd∞t i s nßzvem prostoru jmen, do kterΘho nßle₧φ, jak si m∙₧ete vÜimnout u cout a endl. Jist∞ by bylo velice namßhavΘ pou₧φvat vÜude mφsto jednoduchΘho zßpisu cout std::cout. Proto existuje direktiva using, kterou si zp°φstupnφme cel² prostor jmen. StaΦφ ji uvΘst  za °ßdek s direktivou #include v nßsledujφcφ podob∞:

using namespace std;

    Krom∞ direktivy using existuje i deklarace stejnΘho jmΘna, ale ta zp°φstupnφ pouze jeden zvolen² identifikßtor z n∞jakΘho prostoru jmen. V p°φpad∞, kdy vφme, ₧e nebudeme pot°ebovat veÜkerΘ identifikßtory z prostoru jmen (obzvlßÜt∞ rozsßhlΘho prostoru), je pou₧itφ deklarace using vhodn∞jÜφ. Nßsleduje v²Üe uveden² program, ale jsou zp°φstupn∞ny jen dva identifikßtory:

#include <iostream> // tady opravdu neni .h

using std::cout;
using std::endl;

int main(int argc, char* argv[])
{
    cout << "Nazdar!!!" << endl;
    return 0;
}

    Vidφme, ₧e pro ka₧d² identifikßtor, kter² chceme zp°φstupnit, musφme napsat jednu deklaraci using. U zp°φstup≥ovanΘho identifikßtoru se neuvßdφ typ prom∞nnΘ, nßvratov² typ funkce a neuvßd∞jφ se ani zßvorky po funkci. Pokud bychom tedy cht∞li zp°φstupnit funkci NaDruhou() z prostoru Matika, uvedli bychom:

using Matika::NaDruhou;    // ne NaDruhou()

    Doposud jsme se setkßvali nap°. s proudy pro vstup cin a v²stup cout, kterΘ takΘ pat°φ do prostoru std. Ale my jsme je volali p°φmo, nemuseli jsme p°ed n∞ vklßdat std::. To bylo zp∙sobeno tφm, ₧e pokud p°ekladaΦ zjistφ, ₧e pou₧φvßme soubory s p°φponou .h, pak direktivu using doplnφ sßm. Mo₧nß se ptßte, proΦ byste tedy m∞li pou₧φvat v direktiv∞ #include jmΘno bez p°φpony. Odpov∞∩ najdete nap°. v nejnov∞jÜφ verzi v²vojovΘho prost°edφ Microsoft Visual Studio .NET. Pokud vlo₧φte soubor iostream.h, p°ekladaΦ k≤d sice p°elo₧φ, ale s upozorn∞nφm, ₧e tento soubor ji₧ nebude v dalÜφ verzi v²vojovΘho prost°edφ k dispozici a mßte tedy pou₧φvat soubor iostream.

    Je t°eba dßt pozor, ₧e zp°φstupn∞nφm n∞jakΘho prostoru mohou p°ekladaΦi vzniknout nejasnosti jako v nßsledujφcφ ukßzce:

namespace Prostor
{
    int test;
    int Test() { return test; }
}

class Test {
    int a;
};

using namespace Prostor;

int main(int argc, char* argv[])
{
    Test test;   
// problem dela funkce Test() a trida Test
    return 0;
}

    Krom∞ prom∞nn²ch a funkcφ se m∙₧e v deklaraci prostoru jmen objevit dalÜφ prostor jmen. Potom se jednß o tzv. vno°en² prostor jmen. Pokud chceme p°istupovat k prvk∙m z vno°enΘho prostoru, musφme je uvßd∞t pln²m jmΘnem, tedy vΦetn∞ prostoru nad°azenΘho. P°φklad:

namespace Prostor
{
    namespace PodProstor {
        int test;
        int Test() { return test; }
    }

    int test;
    int NaDruhou(int a) { return a*a; }
}

int main(int argc, char* argv[])
{
    Prostor::PodProstor::Test();
    return 0;
}

    Pokud bychom pot°ebovali do prostoru PodProstor vlo₧it dalÜφ vno°en² prostor, pak p°φstup k jeho prvk∙m by byl velice nep°φjemn² pro zßpis. Jazyk C++ nabφzφ tedy "p°ezdφvky" (aliasy), kterΘ pak umo₧≥ujφ kratÜφ zßpis:

namespace PP = Prostor::PodProstor;

    Po tomto °ßdku m∙₧eme p°istupovat k funkci Test nßle₧ejφcφ do prostoru jmen PodProstor pomocφ nßsledujφcφho zßpisu:

PP::Test();

    Samoz°ejm∞, ₧e nßm nic nebrßnφ v pou₧itφ direktivy using, kterou si m∙₧eme zp°φstupnit cel² PodProstor:

using namespace Prostor::PodProstor;

    V objektov²ch typech (t°φd∞ a struktu°e) nelze pou₧φt direktivu using ke zp°φstupn∞nφ celΘho prostoru jmen, ale lze tam pou₧φt deklaraci using, pomocφ kterΘ jsme upravovali v dφlu 14 p°φstupovß prßva v objektech (vlastn∞ jsme zp°φstupnili prvek z p°edka).

19.1.2. Anonymnφ prostory jmen

    Doposud jsme mluvili o prostorech, kter²m jsme p°id∞lili jmΘno. Pokud se rozhodneme jmΘno nep°id∞lit, vznikß tzv. anonymnφ prostor. Chceme-li zavolat funkci definovanou v tomto prostoru, pou₧ijeme pouze jejφ jmΘno podobn∞ jako by to byla globßlnφ funkce. Stejn² postup pak platφ i pro ostatnφ identifikßtory pat°φcφ do tohoto prostoru.

    P°i p°ekladu jsou vÜechny anonymnφ prostory spojeny v jeden, kterΘmu je navφc p°id∞leno unikßtnφ jmΘno (liÜφ se mezi r∙zn²mi soubory). Tφm pßdem prom∞nnΘ a funkce z prostoru jednoho souboru nelze pou₧φt v souboru jinΘm. Je to jako bychom je prohlßsili za statickΘ (p°φstupnΘ jen v modulu, kde byly definovßny).

19.2. Operßtory p°etypovßnφ

    V minul²ch dφlech jsme se setkali s p°etypovßnφm v nßsledujφcφ podob∞:

(typ)JmenoPromenne

    Jazyk C++ nßm vÜak nabφzφ jeÜt∞ Φty°i specißlnφ operßtory - static_cast, dynamic_cast, const_cast a reinterpret_cast. Tyto operßtory byly zavedeny, aby odstranili n∞kterΘ nejednoznaΦnosti p°i typovΘ konverzi. Nßsledujφcφ k≤d je platn²:

class A {
    int m_iTmp;
};

class B {
private:
    int m_b;
public:
    B() { m_b = 55; }
    void Fce() { return m_b*m_b; }
};

int main(int argc, char* argv[])
{
    A a;
    B *b;
    b = (B*) &a;   
// legalni pretypovani

    b->Fce();
    return 0;
}

    AΦkoliv se tento k≤d na v∞tÜin∞ p°ekladaΦ∙ bez problΘm∙ zkompiluje, je nesprßvn². VÜimn∞te si, ₧e volßme metodu Fce(), ale v programu neexistuje ₧ßdn² objekt typu B. Prom∞nnß b je pouze ukazatelem na b, neexistuje tedy ani Φlenskß prom∞nnß B::m_b. V²sledkem takovΘto operace m∙₧e b²t v lepÜφm p°φpad∞ nesprßvn² v²sledek, v horÜφm pak chyba p°i b∞hu programu.

    Operßtory se zapisujφ v nßsledujφcφ podob∞:

operator_pretypovani<typ>(vyraz);

    Jako operator_pretypovani uvedeme jeden z v²Üe uveden²ch. Vyraz je to, co se mß p°evΘst a typ urΦuje, co bychom cht∞li dostat jako v²sledek konverze.

19.2.1. Operßtor static_cast

    Tento operßtor pouze p°evede vyraz na typ a to pouze na zßklad∞ typ∙ p°φtomn²ch ve v²razu vyraz. Pokud tedy mßme ukazatel na t°φdu, pak static_cast m∙₧e provΘst p°etypovßnφ odvozenΘ t°φdy na jejφ rodiΦovskou t°φdu, ale takΘ nßm umo₧≥uje p°etypovat ukazatel na rodiΦovskou t°φdu na ukazatel na odvozenou t°φdu. Tento operßtor neprovßdφ ₧ßdnΘ ov∞°enφ za b∞hu programu, narozdφl od nφ₧e uvedenΘho dynamic_cast. V prvnφm p°φpad∞ to je platnΘ p°etypovßnφ, kterΘho bychom mohli docφlit i pou₧itφm klasickΘho zßpisu (typ), ale druh² p°φpad zavßnφ problΘmy, pokud nevφme co opravdu d∞lßme. Operßtor se pou₧φvß ve spojenφ s typy, kterΘ nejsou polymorfnφ, tedy neobsahujφ virtußlnφ funkce.

    Tento operßtor lze takΘ pou₧φt ke konverzφm mezi zßkladnφmi datov²mi typy:

double pi=3.14159265;
int celacast = static_cast<int>(pi);

19.2.2. Operßtor dynamic_cast

    Tento operßtor pouze p°evede vyraz na typ a to pouze na zßklad∞ typ∙ p°φtomn²ch ve v²razu vyraz. Typ m∙₧e b²t pouze ukazatel nebo reference na d°φve deklarovanou t°φdu nebo ukazatel na typ void (tedy void *). Typ v²razu vyraz zßvisφ na typu typ. Pokud je typ ukazatelem, pak musφ i vyraz b²t ukazatelem. Pokud je to reference, pak to musφ b²t l-hodnota, tedy v²raz kter² m∙₧e stßt i na levΘ stran∞ rovnφtka p°i p°i°azenφ. Operßtor se narozdφl od static_cast pou₧φvß ke konverzφm polymorfnφch typ∙.

    Jak jsme si uvedli v²Üe, tak dynamic_cast provßdφ kontrolu za b∞hu programu, jestli konverze mß smysl. Mohou nastat dva problΘmy:

    Ukß₧eme si p°φklady na oba p°φpady:

#include <iostream>

using namespace std;

class Rodic { virtual Fce() { ; } };
class Potomek : public Rodic { };

int main(int argc, char* argv[])
{
    Rodic* r1 = new Potomek;
    Rodic* r2 = new Rodic;

    Potomek* p1 = dynamic_cast<Potomek*>(r1);   
// to je v poradku
    Potomek* p2 = dynamic_cast<Potomek*>(r2);   
// tady se vrati NULL

    if(p1 == NULL)
    {
        cout << "Chyba!" << endl;
    }
    else
    {
        cout << "OK!" << endl;
    }
    if(p2 == NULL)
    {
        cout << "Failed!" << endl;
    }

    char c;
    cin >> c;
    return 0;
}

    K tomuto p°φkladu je nutnΘ uvΘst, ₧e ne vÜechny p°ekladaΦe majφ implicitn∞ nastavenou podporu pro dynamickou identifikaci typ∙. To je i p°φpad Microsoft Visual C++ 6.0, po p°ekladu nßs p°ekladaΦ varuje, ₧e se sna₧φme o konverzi pomocφ dynamic_cast bez tohoto nastavenφ a po spuÜt∞nφ programu dojde k vyvolßnφ v²jimky. Pro nastavenφ RTTI (Run Time Type Information - Informace o typu za b∞hu programu) otev°ete menu Project, zvolte Settings. V okn∞ Settings pak vyberte kartu C/C++ a v listboxu Category vyberte C++ Language. ZaÜkrtn∞te volbu Enable RTTI. Program by pak m∞l fungovat sprßvn∞.

19.2.3. Operßtor const_cast

    Jedinou funkcφ tohoto operßtoru je, ₧e modifikuje atributy const, volatile a _unaligned, co₧ ₧ßdn² jin² p°etypovßvacφ operßtor nedokß₧e. M∙₧e tyto atributy bu∩ p°idßvat nebo ubφrat:

class A { /* deklarace */ };
const A *a = new A;
A *b = const_cast<A*> (a);

19.2.4. Operßtor reinterpret_cast

    Tento operßtor p°evede ukazatel jakΘhokoliv typu na jak²koliv jin² typ. Navφc lze pomocφ n∞j p°evΘst libovolnou celoΦφselnou hodnotu na ukazatel, pop°. obrßcen∞. Je jasnΘ, ₧e takovΘto operace jsou velice nebezpeΦnΘ a Φasto nep°enositelnΘ. Rozumn²m pou₧itφm je p°evod ukazatele na jin² a pozd∞jÜφ konverze zp∞t na p∙vodnφ typ. Ostatnφ konverze jsou p°inejmenÜφm nep°enositelnΘ, v tom horÜφm pak nebezpeΦnΘ, jak jsme si uvedli.

class A {};
class B {};
A *a = new A;
B *b = reinterpret_cast<B*>(a);    // ackoliv spolu tridy vubec nesouvisi, lze to pouzit

19.2.5. Upozorn∞nφ

    Operßtory reinterpret_cast a const_cast by se m∞ly pou₧φvat pouze, pokud jinΘ °eÜenφ opravdu nenφ mo₧nΘ. Umo₧≥ujφ toti₧ konverze, kterΘ p°edstavujφ stejnΘ nebezpeΦφ jako p∙vodnφ operßtor p°etypovßnφ (p°φklad byl na zaΦßtku odstavce o operßtorech p°etypovßnφ).
 

19.3. Co bude p°φÜt∞?

    P°φÜt∞ si povφme n∞co o urΦenφ typu za b∞hu programu a pak se budeme v∞novat vstupnφm a v²stupnφm proud∙m. Tφm bude prozatφm p°eruÜen kurz o C++, proto₧e si ud∞lßme v²let do sv∞ta datov²ch struktur, bez nich₧ ₧ßdn² smyslupln² program nem∙₧e existovat. Potom se vrßtφme jeÜt∞ na chvφli ke kurzu C++, konkrΘtn∞ k STL (Standard Template Library), co₧ je knihovna Üablon jazyka C++ usnad≥ujφcφ prßci s n∞kter²mi datov²mi strukturami.

PřφÜtě nashledanou.

Ond°ej BuriÜin