Kombinace C++ a.net jak a proč

Podobné dokumenty
Základy jazyka C# Obsah přednášky. Architektura.NET Historie Vlastnosti jazyka C# Datové typy Příkazy Prostory jmen Třídy, rozhraní

Zápis programu v jazyce C#

PREPROCESOR POKRAČOVÁNÍ

Úvod Třídy Rozhraní Pole Konec. Programování v C# Hodnotové datové typy, řídící struktury. Petr Vaněček 1 / 39

Generické programování

Osnova přednášky. Programové prostředky řízení Úvod do C# II. Přístup ke členům. Členy (Members)

TŘÍDY POKRAČOVÁNÍ. Události pokračování. Příklad. public delegate void ZmenaSouradnicEventHandler (object sender, EventArgs e);

Jazyk C# (seminář 6)

Programování v C++ 2, 4. cvičení

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007

PŘETĚŽOVÁNÍ OPERÁTORŮ

Úvod do programovacích jazyků (Java)

Úvod Seznámení s předmětem Co je.net Vlastnosti.NET Konec. Programování v C# Úvodní slovo 1 / 25

Jakub Čermák Microsoft Student Partner

PROMĚNNÉ, KONSTANTY A DATOVÉ TYPY TEORIE DATUM VYTVOŘENÍ: KLÍČOVÁ AKTIVITA: 02 PROGRAMOVÁNÍ 2. ROČNÍK (PRG2) HODINOVÁ DOTACE: 1

Platforma.NET 4. hodina dnes to bude ideologické

Obsah přednášky 7. Základy programování (IZAPR) Přednáška 7. Parametry metod. Parametry, argumenty. Parametry metod.

8 Třídy, objekty, metody, předávání argumentů metod

Výčtový typ strana 67

Programování v C++ 1, 5. cvičení

typová konverze typová inference

PB161 Programování v jazyce C++ Přednáška 7

PB161 Programování v jazyce C++ Přednáška 7

Pokročilé programování v jazyce C pro chemiky (C3220) Statické proměnné a metody, šablony v C++

Úvod do programovacích jazyků (Java)

PB161 Programování v jazyce C++ Přednáška 4

7. Datové typy v Javě

Jakub Čermák Microsoft Student Partner

1. Programování proti rozhraní

přetížení operátorů (o)

Obsah. Předmluva 13 Zpětná vazba od čtenářů 14 Zdrojové kódy ke knize 15 Errata 15

7 Formátovaný výstup, třídy, objekty, pole, chyby v programech

Jazyk C++ I. Šablony 2

IAJCE Přednáška č. 8. double tprumer = (t1 + t2 + t3 + t4 + t5 + t6 + t7) / 7; Console.Write("\nPrumerna teplota je {0}", tprumer);

Teoretické minimum z PJV

IRAE 07/08 Přednáška č. 1

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007

2) Napište algoritmus pro vložení položky na konec dvousměrného seznamu. 3) Napište algoritmus pro vyhledání položky v binárním stromu.

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007

Programovací jazyk Úvod do programování v C#

Více o konstruktorech a destruktorech

Jazyk C++ II. Šablony a implementace

Pokud neuvedeme override, vznikne v synu nová (nevirtuální) metoda (a pochopitelně se nezavolá, jak bychom

NPRG031 Programování II 1 / :25:46

PB161 Programování v jazyce C++ Přednáška 9

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007

Objektově orientované programování

1. Dědičnost a polymorfismus

Programovací jazyk C# Úvod do programování v C#

Počítačové laboratoře bez tajemství aneb naučme se učit algoritmizaci a programování s využitím robotů CZ.1.07/1.3.12/

Programování v C++ 1, 6. cvičení

Jazyk C++ 1. Blok 3 Objektové typy jazyka C++ Třída. Studijní cíl. Doba nutná k nastudování. Průvodce studiem

NMIN201 Objektově orientované programování 1 / :36:09

Tento studijní blok má za cíl pokračovat v základních prvcích jazyka Java. Konkrétně bude věnována pozornost rozhraním a výjimkám.

NPRG031 Programování II --- 2/2 Z, Zk

PB161 Programování v jazyce C++ Přednáška 4

Assembler - 5.část. poslední změna této stránky: Zpět

Polymorfismus. Časová náročnost lekce: 3 hodiny Datum ukončení a splnění lekce: 30.března

Programovací jazyk C# Úvod do programování v C#

Dědění, polymorfismus

konstruktory a destruktory (o)

Úvod do jazyka C. Ing. Jan Fikejz (KST, FEI) Fakulta elektrotechniky a informatiky Katedra softwarových technologií

7. přednáška - třídy, objekty třídy objekty atributy tříd metody tříd

PB161 Programování v jazyce C++ Přednáška 10

VÝUKOVÝ MATERIÁL. Bratislavská 2166, Varnsdorf, IČO: tel Číslo projektu

Jazyk C++ I. Polymorfismus

Čtvrtek 8. prosince. Pascal - opakování základů. Struktura programu:

Programování v C++ 3, 3. cvičení

4. ZÁKLADNÍ POJMY Z OBJEKTOVĚ ORIENTOVANÉHO PROGRAMOVÁNÍ

Objektové programování

Definice třídy. úplná definice. public veřejná třída abstract nesmí být vytvářeny instance final nelze vytvářet potomky

Mělká a hluboká kopie

Jazyk C++ I. Šablony

Seminář Java II p.1/43

Datové typy v Javě. Tomáš Pitner, upravil Marek Šabo

Objekty v PHP 5.x. This is an object-oriented system. If we change anything, the users object.

1 - Úvod do platformy.net. IW5 - Programování v.net a C#

Algoritmizace a programování

int ii char [16] double dd název adresa / proměnná N = nevyužito xxx xxx xxx N xxx xxx N xxx N

IRAE 07/08 Přednáška č. 2. atr1 atr2. atr1 atr2 -33

Přetěžování operátorů

Chování konstruktorů a destruktorů při dědění

Komponenty v.net. Obsah přednášky

Předmluva k aktuálnímu vydání Úvod k prvnímu vydání z roku Typografické a syntaktické konvence... 20

C++ přetěžování funkcí a operátorů. Jan Hnilica Počítačové modelování 19

Ukazka knihy z internetoveho knihkupectvi

Programování v jazyce C a C++

Programování II. Polymorfismus

Střední škola pedagogická, hotelnictví a služeb, Litoměříce, příspěvková organizace

Programování v Javě I. Únor 2009

Dědičnost (inheritance)

Viditelnost (práva přístupu) Tomáš Pitner, upravil Marek Šabo

Konstruktory a destruktory

Programování v Javě I. Leden 2008

Pokročilé programování v jazyce C pro chemiky (C3220) Operátory new a delete, virtuální metody

Bridge. Známý jako. Účel. Použitelnost. Handle/Body

Obsah přednášky 9. Skrývání informací. Skrývání informací. Zapouzdření. Skrývání informací. Základy programování (IZAPR, IZKPR) Přednáška 9

Programovací jazyk Pascal

Seznámení s prostředím dot.net Framework

Definice třídy. úplná definice. public veřejná třída abstract nesmí být vytvářeny instance final nelze vytvářet potomky

Transkript:

Kombinace C++ a.net jak a proč Aleš Keprt Katedra informatiky, Přírodovědecká fakulta, Univerzita Palackého Tomkova 40, 779 00 Olomouc, Česká Republika Ales.Keprt@upol.cz Abstrakt. Před příchodem platformy.net bylo ve Windows primárním programovacím jazykem C++, v rámci.netu se však používá především nový jazyk C#. Cílem tohoto příspěvku je vysvětlení, jak lze programovat v jazyku C++ na platformě.net a také diskuze o tom, k čemu je vlastně dobré, když naučíme starého psa novým kouskům. Příspěvek se zaměřuje na variantu jazyka zvanou C++/CLI, která je nyní preferovaná pro programování v C++ v rámci.netu. Klíčová slova: C++,.NET, programování, Windows 1 Úvod do C++/CLI Úvodem se krátce zastavme u samotného názvu jazyka. C++/CLI označuje úpravu jazyka C++ pro použití v rámci Microsoft.NET Frameworku. Samotný.NET Framework je přitom pouze jednou z implementací obecného standardu Common Language Infrastructure, odtud tedy pochází ona tří písmena CLI v názvu jazyka. Přitom, ačkoliv jazyk je pojmenován obecně, jeho v současnosti zřejmě jedinou implementací je Microsoft Visual C++ 2005. Základem jazyka je standardní C++, odpovídající zhruba ISO standardu, doplněné o celou řadu sémantických i syntaktických prvků sloužících k překlenutí sémantické mezery mezi programovým modelem CLI a nativními programy. Nejviditelnějším a také nejčastěji jmenovaným rozdílem je automatická správa paměti v rámci CLI zatímco v C++ bývá zvykem uvolňovat paměť operátory delete a delete[], CLI zajišťuje správu paměti automaticky. Ještě podstatnější rozdíly však najdeme při hlubším prozkoumání. Základem CLI je CTS (Common Type System společný typový systém) definující společné základní datové typy, CLS (Common Language Specification) definující pravidla, která musí dodržet vnější rozhraní kódu v každém jazyku, aby mohl být kombinován s kódem napsaným v jiném jazyku, a běhové prostředí CLR (Common Language Runtime), které se stará o vlastní vykonávání kódu. Zatímco C++ překládá kód (obvykle přes Assembler) do nativního kódu pro určitý mikroprocesor, C++/CLI se překládá do mezijazyka CIL (Common Intermediate Language, také známo jako MSIL Microsoft Intermediate Language nebo jen zkráceně IL), ze kterého se nativní kód vytváří až po spuštění daného programu. Kód v mezijazyku má tu výhodu, že může být přeložen do

různých nativních tvarů, podle použitého počítače; CLI přitom klade velký důraz hlavně na bezpečnost, proto taky ani není možno programy přímo překládat do nativního tvaru. Naopak, kód v mezijazyku může být podroben verifikaci ověření bezpečnosti. Z hlediska typové bezpečnosti je u C++ principiální problém pointery. CLI umožňuje označit kód jako bezpečný nebo nebezpečný. Zatímco se C#, Visual Basic.NET nebo jiné moderní jazyky orientují především na bezpečný kód a nebezpečný kód v nich lze psát jen s pomocí speciálních konstrukcí, nebo vůbec, v C++/CLI máme z principu otevřené možnosti kombinovat klasický programovací styl C++ (s pointery) a bezpečné programování ve stylu CLI (čili.netu). Co zbylo z ISO C++ Co nám zde tedy zbylo z ISO C++? Základem C++/CLI je kompletní funkcionalita jazyka C++ na úrovni překladače Microsoftu (Visual C++), je možno vkládat dokonce i inline Assembler. Překladač jazyka podporuje bezpečný, nebezpečný i nativní kód. Z toho důvodu je C++/CLI také vhodným jazykem pro situace, kdy chceme převést existující projekt na.net, ale přitom vlastně nechceme přepisovat existující zdrojové kódy. To je obvyklé u větších projektů obsahujících jistou hloubku kódu, kde do.netu převedeme jen povrchové části kódu. (Hloubka kódu je rozsah výpočetních částí programu, které jsou napsány přímo v C++ bez použití nějakých knihoven pro spolupráci s vnějším prostředím. Například informační systémy mají obvykle naprosto minimální hloubku, naopak software pro vědeckotechnické výpočty má hloubku velkou. Termín povrchová část kódu označuje jeho vnější rozhraní, čili uživatelské rozhraní, rozhraní pro práci s databází apod.) Oblasti nasazení C++/CLI Ačkoliv je C++/CLI na první pohled jakými zbytečným jazykem, který jen dubluje jazyk C#, existuje několik oblastí, kde má C++/CLI oproti svému protějšku jednoznačně navrch. Hodí se především tam, kde potřebujeme rozsáhlou součinnost mezi řízeným a nativním kódem. Také je vhodný pro převod existujícího kódu z C++ do C#, neboť umožňuje používat mnoho konstrukcí C++, které v jazyku C# nemají svůj ekvivalent. Dále je samozřejmě vhodný pro programátory, kteří umějí velmi dobře C++ a jazyk C# se ani nechtějí učit. Jazyk C++/CLI zároveň nahrazuje dřívější Managed C++ jazyk vzniklý za stejným účelem, ovšem s mnohem menším úspěchem. V neposlední řadě je C++/CLI doporučován také pro tvorbu systémových služeb, což zde jde daleko snáze než s pomocí C++ a knihovny ATL. 2 Nové prvky jazyka C++/CLI Jazyk C++/CLI rozhodně nepatří k nejjednodušším, spíše naopak. K jeho pochopení je nutno rozumět samotnému C++, platformě.net a také implementaci 2

C++ do CLI. Nejlepším výchozím bodem pro studium C++/CLI je znalost a praxe s ISO C++ a programování na platformě.net v jazyce C#. Z tohoto bodu také vychází následující text, tj. předpokládáme znalost jak jazyka C++, tak platformy.net s jazykem C#. Výklad C++/CLI pak lze provést formou popisu změn oproti C++ a C#. Hello World Na úvod je vhodné ukázat Hello World aplikaci pro srovnání nejprve v jazyku C#: using System; class Program { static void Main(string[] args) { Console.WriteLine("Hello World"); a nyní v jazyku C++/CLI: using namespace System; int main(array<string^> ^args) { Console::WriteLine("Hello World"); return 0; Na tomto příkladu vidíme několik základních rozdílů mezi těmito dvěma jazyky: C++/CLI používá nový operátor ^ k označení řízených referencí (namísto běžných pointerů *), operátor :: k přístupu do složek tříd a prostorů jmen (namísto tečky v C#) a poněkud složitější syntaxi řízených polí (neboť [] je vyhrazeno pro klasická neřízená pole). Funkce main() také standardně vrací typ int, zatímco v jazyku C# toto není. Kontextová klíčová slova Autoři C++/CLI přišli se zajímavým řešením, jak rozšířit jazyk o nová klíčová slova a zároveň neomezit použití těchto slov pro identifikátory (názvy proměnných apod.). Nová klíčová slova jsou tedy kontextově závislá, čili jsou platná jen v určitém kontextu, navíc jsou vesměs řešena jako doplňující slova k stávajícím klíčovým slovům (která slouží právě k identifikaci kontextu platnosti nových klíčových slov). Například value class jsou dvě klíčová slova, pokud stojí vedle sebe, přitom lze zakládat i proměnné (nebo jiné entity) jménem value. Tuto vlastnost oceníme především při převodu staršího kódu, který může některá nyní klíčová slova používat jako identifikátory (například právě slovo value se v názvech vyskytuje běžně). Obecně se tedy nová klíčová slova zapisují před nebo za stávající, nebo do jiných míst v kódu, která lze kontextově jednoznačně identifikovat. Konkrétní případy budou popsány dále v textu. 3

Práce s řízenými typy C++/CLI umožňuje používat klasické datové typy se vším všudy, ty označujeme jako nativní či neřízené, a dále řízené typy v rámci CLI, které mají automatickou správu paměti a sadu zvláštních klíčových slov a operátorů, které s nimi pracují. Všechny typy je možno v kódu libovolně kombinovat. Kód používající nativními typy může někdy běžet rychleji, záleží však na konkrétním algoritmu a nevýhodou je zde nebezpečí paměťových chyb, které je často obtížné odhalit. Příkladem řízeného typu je String (System::String). C++/CLI automaticky typuje textové literály (jako Hello World ) jako const char*, const wchar t* nebo String. Používá přitom typovou inferenci, čili podle toho, kam se literál přiřazuje, takový má typ. Explicitně lze typ ovlivnit použitím prefixu L jako v C++ (wchar t*) nebo prefixem (String^). V obou případech, ačkoliv ten druhý syntakticky odpovídá přetypování, je přímo vytvořen literál daného typu, ke zbytečným přetypování tedy nedochází. Vytvoření a použití vlastních typů vysvětlíme nejlépe na příkladě: ref class RefTyp { public: int i; ; value class ValTyp { public: int i; ; enum class Typ3 { Jaro, Léto, Podzim, Zima, ; int main(array<string^> ^args) { RefTyp ^a = gcnew RefTyp; ValTyp b; ValTyp ^c = gcnew ValTyp; a->i = 1; b.i = 2; c->i = 3; Console::WriteLine(Typ3::Jaro); return 0; Na konci deklarace typu se píše středník. Místo class lze psát také struct, výchozí přístup pak bude public. Hodnoty řízených výčtových typů jsou kontextové, čili narozdíl od C++ zde nejde o globální konstanty (viz příklad použití na konci programu). 4

Deklarace řízených referenčních a hodnotových typů se provádí přidáním prefixu ref nebo value. Jak je vidět v ukázce použití, instance referenčních typů se vytvářejí operátorem gcnew (proměnná a), zatímco hodnotové typy jsou deklarovány přímo (proměnná b je na zásobníku v rámci funkce). Proměnná c zde vyjadřuje referenci na hodnotový typ, což je vnitřně implementováno boxováním na řízené haldě je tedy boxovaná hodnota a při každé změně je třeba vytvořit novou instanci a boxovat ji, proto je tento způsob používání řízených hodnotových typů značně neefektivní. Namísto klasických pointerových hvězdiček se u řízených referenčních typů používá nová značka (operátor) ^ a místo o pointerech hovoříme o handlech (anglicky handle česky držátko/rukojeť, obvykle ale nazýváno lidovým označením handl ). Pro přístup k součástem tříd, nutno dodat bohužel, musíme použít operátor -> a u statických součástí, opět bohužel, ::. A aby to nebylo tak jednoduché, u řízených hodnotových typů na zásobníku je nutno používat operátor tečka. Při záměně těchto operátorů nás překladač na chybu upozorní (což ovšem také dokazuje, že by stačil jeden operátor nejlépe klasická tečka, neboť z kontextu vždy lze jasně poznat význam). V tomto směru tedy C++/CLI kráčí ve šlépějích C++ a vlastně úplně zbytečně ještě přidává na jeho syntaktické nesrozumitelnosti. (Operátory :: a -> by zde mohly být nahrazeny tečkou.) Řízené typy mají oproti nativním ještě dvě důležitá omezení: Nelze z nich zakládat globální proměnné, ani pomocí handlů, a není podporována vícenásobná dědičnost. Důvod prvního omezení je poněkud nejasný, zvláště když nativní C++ globální proměnné podporuje. Druhé omezení pramení ze standardu CLI, který vícenásobnou dědičnost nepodporuje. Null a boxování Zatímco jazyk C nevhodně definoval bezkontextovou konstantu NULL, i když umožňoval přiřazovat číslo 0 do pointerových proměnných, a jazyk C++ používá pro tento účel přímo číslo 0 (i když mnozí programátoři stále nevhodně používají NULL z jazyka C), v C++/CLI jsme nuceni rozlišovat null u řízených a nativních typů. Zatímco u nativních typů je vše jako v C++, do řízených typů nelze přiřazovat číslo 0. Namísto toho musíme použít klíčové slovo nullptr. Přiřazení čísla 0 je zvlášť nebezpečné v následujícím případě: object ^o = 0; Tento kód totiž do proměnné o přiřadí boxované číslo 0 typu Int32. Důvodem je to, že C++/CLI podporuje implicitní boxování hodnotových proměnných. Zpětný unboxing je explicitní, takže obojí se chová stejně jako v C#. Rozdíl oproti jazyku C# je ale v tom, že C++/CLI umožňuje kromě boxování pomocí proměnné typu object^ vytvářet také silně typované boxované proměnné, viz proměnnou c výše. Tuto konstrukci jazyk C# nezná. Předávání parametrů hodnotou a odkazem CTS podporuje dva typy předávání parametrů metodám: hodnotou (kopií) a odkazem (referencí). C++/CLI se v tomto směru chová stejně jako C#: Výchozí 5

je předávání parametrů hodnotou a chceme-li nějaký předat odkazem, explicitně jej označíme. C++/CLI k tomu používá nový unární operátor %, jehož použití je principiálně stejné jako u nativního operátoru & (který také slouží k předávání parametrů odkazem), operátor % však slouží pro řízené typy, v jazyku C# mu odpovídá prefix ref. Narozdíl od C# zde však při volání metody nemusíme znovu označovat předávané argumenty, překladač sám ví, že je má předávat odkazem. Čili zde je syntaxe C++/CLI oproti C# poněkud příjemnější. Aby nedošlo k omylu, ještě připomeňme, že je velký rozdíl mezi referenční proměnnou předanou hodnotou a odkazem. V obou případech používáme stejný termín reference neboli odkaz, jenže zatímco v prvním případě je řeč o proměnné typu reference na objekt a předáváme hodnotou jen onu referenci (zatímco objekt nepředáváme), ve druhém případě předáváme referenci na onu proměnnou. U referenční proměnné tedy můžeme předat onu proměnnou, tedy referenci, nebo odkaz na onu proměnnou, tedy referenci na referenci. Podobně při předávání hodnotové proměnné hodnotou se předává (kopíruje) celý objekt a při předávání hodnotové proměnné referencí se předává odkaz na původní hodnotovou proměnnou. Jelikož referenční parametry a referenční proměnné nejsou tímtéž, C++/CLI pro jejich rozlišení používá dva různé operátory % a ^. Modifikátory přístupu Kromě základních modifikátorů public/protected/private, které jsou syntakticky i sémanticky stejné jako v C++, můžeme použít ještě další modifikátory k nastavení viditelnosti mezi různými seskupeními (assembly) v rámci CLI. Seznam modifikátorů a jejich vysvětlení je v tabulce 1. modifikátor Tabulka 1. Modifikátory přístupu přístup uvnitř assembly mimo assembly public public public private private private protected protected protected internal public private protected public public protected private protected protected private Při dědění v rámci řízených typů se za dvojtečku již nepíše modifikátor přístupu, narozdíl od C++ je zde automaticky přístup public a nelze jej změnit. Toto chování odpovídá jak standardu CLI, tak i obvyklému postupu při psaní kódu v C++, kde public ve většině případů stejně ručně dopisujeme. 6

Sémantika destrukce objektů Nativní typy jsou ukončovány stejně jako v C++, tedy operátorem delete nebo delete[]. Doba života řízených typů je automaticky řízena systémem. Toto se však týká pouze paměti, zatímco při používání systémových zdrojů nebo jakýchkoliv jiných neřízených součástí je třeba zajistit správné uvolnění všech prostředků. Zde se sémantika C++/CLI liší jak od C++, tak od C#. Deklarujeme-li klasický vlnovkový destruktor v řízené třídě, tento je nativně přeložen do metody Dispose(), přitom je automaticky implementováno rozhraní System::IDisposable. Tento destruktor můžeme explicitně volat pomocí klasické konstrukce ~Typ(). Dealokační scénář by měl odpovídat doporučením CLI, čili je třeba deklarovat také finalizer (což mimochodem odpovídá destruktoru v jazyce C#). Finalizer je deklarován podobně jako destruktor, ale s uvedením vykřičníku místo vlnovky před názvem typu. Následuje ukázka doporučeného řešení. ref class FinClass { //destruktor - ukončuje řízené zdroje a volá finalizer ~FinClass() {... this->!finclass(); //finalizer - ukončuje neřízené zdroje!finclass() {... ; Na konci destruktoru tedy voláme vlastní finalizer (Pozor! Je nutno jej volat odkazem přes this.), překladač sám doplní kód zajišťující, aby se po zavolání destruktoru finalizer již nevolal (toto ošetření v C# musíme udělat ručně). Na tomto místě je vidět, že C++/CLI je novější než C# a jeho autoři se poučili z chyb v návrhu jazyka C#. Koncept destrukce objektů v C++/CLI je totiž jednoznačně lepší a jako takový by se jistě hodil i do příští verze jazyka C#. Základní koncepční rozdíl lze popsat takto: C# vznikl v době kdy mezi odborníky převládalo přesvědčení, že správný princip zní garbage collector místo destruktoru. O několik let novější jazyk C++/CLI již funguje na principu garbage collector a destruktor a praxe ukazuje, že tento model správy paměti je lepší. S modelem destrukce souvisí i možnost používat hodnotové proměnné řízených referenčních typů, které můžeme vytvořit jak v rámci metody, tak v rámci jiné třídy. Příklad následuje. ref class A {... ; ref class B { 7

... A a; //lokální instance typu A ; void Metoda() { A a; //lokální instance typu A B b; //lokální instance typu B C++/CLI u těchto proměnných zajišťuje automatické volání destruktorů v okamžiku ukončení nadřazeného bloku kódu či objektu. I tento prvek jde za hranice C#, C++/CLI je zřejmě dokonce prvním jazykem umožňujícím tento pohodlný způsob instanciace a řízené destrukce objektů referenčních typů. V jazyku C# lze toto pouze částečně nahradit blokovým příkazem using. Míchání typů Nativní a řízené typy lze míchat jen velmi omezeně, nativní typ totiž nemůže obsahovat řízenou referenci (handl) a naopak řízený typ nemůže obsahovat nativní instanci. Řízené typy mohou obsahovat pouze pointery na neřízené typy; vytvoření a ukončení nativních objektů pak musíme pečlivě ošetřit v konstruktoru, destruktoru a finalizeru řízené třídy. Hodnotové řízené typy, které neobsahují žádné řízené reference, sémanticky splývají s nativními typy, proto je lze umístit uvnitř neřízeného typu i kdekoliv jinde v neřízené oblasti paměti. Příkladem takového typu je výše uvedený Val- Typ. Naopak následující příklad ukazuje řízený hodnotový typ, který do neřízené paměti umístit nelze. value class ValTyp2 { public: int i; RefTyp ^r; ; Vestavěné datové typy Podobně jako C#, také C++/CLI umožňuje kombinovat vlastní typy (int, char, apod.) a typy CTS (System::Int32, System::Char, apod.). Stejně jako u řetězcových literálů, také číselné literály je možno volně přiřazovat do nativních i řízených číselných typů s využitím typové inference. Zde je pouze třeba pamatovat na to, že některé typy se mapují na typy jiných názvů, vybrané odlišnosti ukazuje tabulka 2. Modifikátory proměnných Kromě modifikátorů přístupu (neboli viditelnosti) lze u proměnných řízených typů použít ještě některé další modifikátory. 8

Tabulka 2. Mapování vybraných datových typů Nativní typ Řízený typ int Int32 char SByte short Int16 long Int32 long long Int64 wchar t Char float Single double Double const označuje proměnnou, jež nelze měnit. Při použití u parametrů metod se chová se stejně jako v C++, čili jde o omezení, které pouze vynucuje překladač. Je-li například vstupní parametr funkce označen jako const, lze jej po vhodném přetypování změnit (za předpokladu, že argumentem je proměnná). Tento modifikátor v C# neexistuje. (C# označení const jazyka C++/CLI nezná, takže jej ignoruje.) literal označuje literál, tedy konstantu vkládanou přímo na místa použití. Literály jsou uloženy pouze v metadatech a nezvětšují velikost objektů dané třídy. Odpovídá označení const v C# a v některých případech také const v C++ (tam, kde C++ vytváří literály). initonly označuje proměnnou, kterou je možno nastavit jen v konstruktoru a dále je jen pro čtení. Toto odpovídá modifikátoru readonly v jazyce C#. unsigned označuje neznaménkovou proměnnou. Chová se stejně jako v C++. Z popisu je vidět, že označení const, literal a initonly všechny znamenají přibližně totéž a připomínají označení const v jazyce C++. Modifikátory literal a initonly definují jasnější a typově bezpečný způsob chování proměnných, zatímco const je anachronizmus z jazyka C++. Modifikátory tříd U řízených i nativních tříd lze kromě modifikátorů přístupu použít ještě tyto další modifikátory: abstract označuje abstraktní třídu. sealed označuje třídu, kterou nelze dědit. Zvláštností je, že tyto modifikátory se píší za název třídy (nikoli před něj, jak bychom mohli čekat jedná se o speciální kontextová klíčová slova). Výjimky Narozdíl od C++, kde bylo z principu nemožné využívat výjimky ke zcela čistému a bezpečnému zachycení a ošetření chyb, C++/CLI nabízí stejnou funkcionalitu jako C#. Výjimky tedy mohou být nativních i řízených typů a nově je možno používat i blok finally, který je významově stejný jako v C#. 9

Statický konstruktor Řízené třídy mohou mít statický konstruktor. Ten je zavolán vždy až těsně před prvním použitím třídy, takže je například možné volat z něj Console::WriteLine() apod. Toto je jistě zajímavý nový prvek oproti C++. U nativních tříd statický konstruktor podporován není. Příkaz for each Nově máme k dispozici příkaz for each (vznikl doplněním kontextového klíčového slova each za klíčové slovo for). Až na mezeru v názvu je použití syntakticky i sémanticky podobné jako v jazyku C#. Příkaz for each je přitom použitelný nejen na řízené objekty implementující rozhraní IEnumerable, ale také na nativní kolekce STL. To je jistě zajímavá novinka. Šablony a generiky C++/CLI umožňuje používat jak generiky z CLI, tak klasické šablony z C++. Klasické šablony přitom lze používat i pro řízené typy, kvůli odlišným operátorům pro nativní a řízenou referenci (* a %) je však někdy třeba napsat dvě úplně stejné deklarace šablony, jednou s operátorem * a podruhé s operátorem %. I přes tuto nepříjemnost je to však další místo, kde C++/CLI předčí jazyk C#. Pro příklad uveďme šablonu funkce na záměnu hodnot dvou proměnných první verze funguje pro nativní typy a také pro řízené hodnotové typy: template <typename T> void swap(t &a, T &b) { T c = a; a = b; b = c; Druhá verze funguje pro všechny nativní i řízené typy; u řízených referenčních typů je ale nutno mít definované příslušné operátory (copy konstruktor a přiřazovací operátor): template <typename T> void swap(t %a, T %b) { T c = a; a = b; b = c; Podobně můžeme definovat i generiku, kterou navíc můžeme poskytovat jako veřejnou součást seskupení (generiky však fungují jen pro řízené typy): 10

generic <typename T> void swap(t %a, T %b) { T c = a; a = b; b = c; Rozdíly mezi šablonami a generikami jsou následující: Generiky jsou instanciovány až při běhu programu, šablony již při překladu. Odtud také pochází většina ostatních rozdílů. Generiky jsou nativně podporovány CLI. Můžeme tedy napsat například generickou třídu pro kolekci v C++ a později ji instanciovat v jiném jazyku doplněním třídy, která v době psaní naší generické třídy nebyla vůbec známa. Generika instanciovaná nad stejným typem ve dvou různých seskupeních je rozpoznána jako stejný typ. Šablona instanciovaná nad stejným typem ve dvou různých seskupeních je považována za dva různé (stejně se chovající) typy. Každá generika má jedinou instanci kódu společnou pro všechny referenční typy dosazené jako parametr, šablony mají samostatnou instanci kódu pro každý typ dosazený jako parametr. Generiky neumožňují beztypový parametr (ve stylu template <int i>). Generiky neumožňují explicitní specializaci ani úplnou, ani částečnou. Generiky neumožňují, aby typový parametr byl zároveň bázovou třídou generické třídy. Generiky neumožňují výchozí hodnoty typových parametrů. Parametrem šablony může být jiná šablona, generiky ani toto neumožňují. První bod seznamu také vysvětluje, proč v programu nelze generiky a šablony libovolně kombinovat. Jelikož šablona musí být instanciována již při překladu programu, nelze ji umístit jako vnitřní složku generiky, ani nelze v generice použít šablonu tak, že parametr generiky by zároveň byl parametrem šablony. Obráceně to však možné je: Šablona může svůj typový parametr použít jako typový parametr generiky. Pole Pole v C++ jsou jen za sebou v paměti umístěné proměnné stejného typu. Způsob práce s poli je velmi podobný práci s pointery, takže i pole patří k nejčastějším zdrojům chyb v programech. V rámci CTS jsou pole řízené referenční třídy, díky čemuž je zajištěno, že při práci s položkami pole jsou vždy z principu kontrolovány meze (přístup před první nebo za poslední prvek pole je vždy spolehlivě odhalen), příjemné je také to, že máme vždy k dispozici informaci o velikosti pole (a díky CTS také o typu každého objektu do pole vloženého). C++/CLI zachovává původní syntaxi pro nativní pole, zatímco řízená pole mají nový způsob deklarace a teprve při používání je možno psát kód běžným 11

způsobem s hranatými závorkami. K deklaraci řízeného pole použijeme následující deklaraci: array<string^> ^pole = gcnew array<string^>(10); array<double,2> ^matice = gcnew array<double,2>(3,3); Syntaxe je tedy podobná jako u generik. Za úvodní slovo array uvedeme do lomených závorek typ prvků v poli a nepovinně dimenzi pole (výchozí je 1). Konstrukci objektu pole provedeme opět uvedením téže konstrukce se slovem array a jako parametry konstruktoru uvedeme požadovanou velikost pole v jednotlivých dimenzích. Na řízených polích můžeme provádět operace definované ve třídě System::Array. Pro přístup k jednotlivým položkám pole použijeme hranaté závorky, u vícerozměrných polí uzávorkujeme všechny indexy dohromady. Viz následující příklad. matice[0,2] = 2; Array::Sort(pole); Pole mají v CTS zvláštní podporu, takže i přes poněkud složitější syntaxi je metoda Sort v tomto příkladu vykonána přímo na našem poli, nejde o kód, který by byl nějak zpomalován dodatečným zjišťováním typu pole až za běhu programu apod. Inicializaci pole lze provést též dosazením hodnot za volání konstruktoru nebo přímým uvedením seznamu hodnot namísto gcnew. Následující dva řádky jsou tedy ekvivalentní: array<int> ^pole = gcnew array<int>(3) {1,2,3; array<int> ^pole = {1,2,3; Dodejme ještě, že array není klíčovým slovem. Jedná se o speciální konstrukt patřící do prostoru jmen cli, v programu tedy můžeme mít třeba proměnnou jménem array a pole pak deklarujeme pomocí cli::array. Funkce volatelné s proměnlivým počtem parametrů U metod s proměnlivým počtem parametrů se rozlišuje řízený a nativní kód. Použijeme-li klasickou deklaraci s výpustkou (...), překladač ji označí varováním (warning) a přeloží metodu do nativního kódu, neboť v rámci řízeného kódu jazyk C++/CLI proměnlivý počet parametrů nepodporuje. Stejně jako C# však můžeme deklarovat za poslední parametr metody speciálně označené pole a tuto metodu pak lze volat syntakticky stejně jako metodu s proměnlivým počtem parametrů. Pro tyto účely se doplní výpustka před deklaraci parametru typu pole, jak ukazuje následující příklad. void test(... array<system::string ^> ^args) { //kód metody 12

Takto deklarovanou metodu můžeme volat s libovolným počtem parametrů, uvnitř ní pak máme všechny tyto parametry k dispozici v poli jménem args (a pomocí pole je to i fyzicky implementováno, přestože CLI podporuje i proměnlivý počet parametrů.) Přetěžování operátorů u řízených typů Přetěžování operátorů funguje u nativních tříd jako v C++, zatímco u řízených tříd funguje stejně jako v C#. Důvodem podobnosti s jazykem C# je zřejmě přímé mapování tohoto konstruktu do CTS. Operátory tedy definujeme jako statické metody, to se týká i operátorů pro přetypování (kde opět stejně jako v C# máme k dispozici klíčová slova implicit a explicit). Narozdíl od C#, jazyk C++/CLI umožňuje definovat i další operátory formou (nestatických) členských metod. Například lze definovat operátor přiřazení = jako metodu jménem operator= nebo i operátor čárka, chování je zde tedy stejné jako v C++. Tyto operátory se nemapují přímo do CTS a jsou platné pro instance typů, u kterých jsou definovány, nikoli pro handly (toto odpovídá chování C++, ale programátory zvyklé na C# by to mohlo zmást). Property Kromě metod a proměnných (fieldů) může řízená třída obsahovat také property (čili česky vlastnosti, ačkoliv tento překlad je značně zavádějící). Ačkoliv se jedná jen o syntaktický cukr, který ani CTS přímo nepodporuje, v nativních typech je možno deklarovat property pouze ve stylu dřívějších verzí Visual C++ (čili pomocí declspec(property)). Property v C++/CLI mají podobný význam jako v C#, ale nabízejí poněkud širší možnosti. Syntaxe je patrná z následujícího příkladu. class PropTest { int vnitrni; property int Hodnota { int get() { return vnitrni; void set(int value) { vnitrni = value; ; Deklarace property je uvozena kontextovým klíčovým slovem property, pak následuje typ a jméno. V následujícím bloku jsou uzavřeny dvě metody nazývané akcesory get slouží ke čtení a set k zápisu hodnoty do property. Smyslem property je syntakticky zpřístupnit vnitřní proměnnou, aniž by tato musela být přímo veřejná (public). Narozdíl od výše uvedeného jednoduchého příkladu, v praxi do kódu akcesorů můžeme napsat libovolný kód, který se vykoná při pokusu o čtení nebo zápis (a při zápisu nás to zajímá především) proměnné. Další vlastnosti property (zde je vidět nevhodnost překladu property jako vlastnost jistě nebudeme říkat vlastnosti vlastností uvádí následující seznam: 13

Jméno property nesmí být stejné jako jméno třídy, ve které je obsaženo. Jednotlivé akcesory mohou být označeny virtual nezávisle na sobě. Je-li property označeno jako statické, musí být statické oba akcesory. Můžeme definovat také indexované property, což sémanticky odpovídá překrytí operátoru přístupu do pole [ ]. Property přitom mohou být i přetížené, čímž definujeme přístup jakoby to vícerozměrných polí o různých dimenzí. Pojmenujeme-li property default, jedná se o výchozí property, které umožňuje použít operátor [ ] přímo na řízený handl. Tento konstrukt tedy odpovídá indexeru v jazyce C#. Syntaxi indexovaných property ukazuje následující příklad, kde položky vektory x,y,z zpřístupníme také ve formě pole. ref class Vektor { public: int x,y,z; ; property int default[int] { int get(int i) { if(i==0) return x; if(i==1) return y; if(i==2) return z; throw gcnew Exception(); Traity void set(int i, int v) { if(i==0) x=v; else if(i==1) y=v; else if(i==2) z=v; else throw gcnew Exception(); Traity jsou dalším typem vlastností (anglicky trait, česky opět vlastnost a opět s jiným významem než atribut nebo property). Se sémantikou traitů se můžeme setkat v STL kolekcích jazyka C++, používají se především z důvodu absence metadat u základních datových typů. (Bez traitu například nelze zjistit minimální platnou hodnotu vyjádřitelnou typem int.) C++/CLI nabízí několik speciálních konstruktů umožňujících nám zjistit informace, které překladač při překladu zná, ale nemají syntaktickou podporu přímo v C++. Tyto konstrukty, nazývané také traity, shrnuje tabulka 3. Zjištění typu Ke zjištění typu můžeme použít konstrukt typ::typeid (pro daný typ), který vrací instanci System::Type pro daný řízený typ. 14

Tabulka 3. Traity Trait Vrací true jestliže... has assign Typ definuje přiřazovací operátor. has copy Typ definuje kopírovací konstruktor. has finalizer Typ definuje finalizer. has nothrow assign Kopírovací přiřazovací operátor typu je definován s prázdnou specifikací výjimek (deklarovanou pomocí throw() před tělem metody operátoru). has nothrow constructor Výchozí konstruktor typu je definován s prázdnou specifikací výjimek. has nothrow copy Kopírovací konstruktor typu je definován s prázdnou specifikací výjimek. has trivial assign Typ má triviální přiřazovací operátor (automaticky generovaný překladačem). has trivial constructor Typ má triviální výchozí konstruktor (automaticky generovaný překladačem). has trivial copy Typ má triviální kopírovací konstruktor. has trivial destructor Typ má triviální destruktor. has user destructor Typ má uživatelem definovaný destruktor. has virtual destructor Typ má virtuální destruktor. (Destruktory řízených typů jsou vždy virtuální.) is abstract Typ je abstraktní. is base of První typ je předkem druhého. is class Typ je nativní třída nebo struktura. is convertible to První typ lze převést (zkonvertovat) na druhý. is delegate Typ je delegát. is empty Typ nemá žádné instanční datové členy. is enum Typ je nativní výčtový typ (enum). is interface class Typ je řízené rozhraní (interface class). is pod Typ je třída nebo unie bez konstruktoru a privátních či protected nestatických členů, nic nedědí, nemá virtuální funkce. False pro všechny fundamentální typy. is polymorphic Typ má polymorfní metody. is ref array Typ je řízené pole. is ref class Typ je referenční třída. is sealed Typ je řízená či nativní zapečetěná třída. is simple value class Typ je hodnotová třída bez referencí na referenční typy. is union Typ je unie. is value class Typ je hodnotová třída (value class). Pro nativní typy můžeme použít operátor typeid, funguje stejně jako v C++. Interop (součinnost s nativním kódem) Termínem Interop (z anglického interoperability součinnost) je v souvislosti s.net Frameworkem označována spolupráce řízeného a neřízeného kódu, přitom musíme rozlišit součinnost na úrovni COM komponent a součinnost na úrovni 15

DLL souborů. Překladače jednotlivých jazyků musí explicitně řešit pouze druhý případ součinnost s DLL knihovnami. Schopnosti jazyka C++/CLI jsou v tomto směru na velmi dobré úrovni. Na výběr máme dvě možnosti, jak součinnost realizovat: PInvoke a IJW. PInvoke (z anglického Platform Invoke) funguje stejně jako v jazyku C#. Deklarujeme tedy mapování funkce z DLL knihovny do řízeného kódu, CLR zajistí správné předání parametrů. IJW (z anglického It Just Works) je zvláštností jazyka C++/CLI. Jak sám název napovídá, C++/CLI umožňuje přímo volat nativní kód z řízeného, stačí inkludovat příslušný hlavičkový soubor a linkovat s příslušnou knihovnou. Narozdíl od PInvoke zde nedochází k žádnému zpracování předávaných argumentů, ty tedy musejí odpovídat deklaraci. Například při práci se stringy to znamená, že musíme ručně převést řetězec z objektu typu String na obvyklý tvar char*. Příklad následuje. char *t = (char*)marshal::stringtohglobalansi("text").topointer(); puts(t); FreeHGlobal(t); Pracujeme-li tedy s řetězci nebo jinými řízenými proměnnými, IJW vyžaduje poněkud složitější kód z důvodu ručního převodu parametrů. Obyčejné hodnoty (například Int32) můžeme samozřejmě použít přímo, navíc v případech, kdy nativní kód voláme častěji se stejnými parametry mohou efektivně udělané ruční převody zrychlit běh programu. IJW je také výhodné pro programy, které používají málo řízených proměnných a většinou pracují s nativními. V tom případě je používání IJW jak efektivní, tak i pohodlné. Se součinností souvisí také několik dalších prvků. Prostor jmen System::Runtime ::InteropServices obsahuje podpůrné prostředky, včetně výše použité třídy Marshal. Klíčové slovo pin ptr umožňuje získat pinning pointer, tedy pointer na řízený objekt, kde CLI zaručuje, že nedojde k přesunu cílového objektu během života tohoto speciálního pointeru. Zamykání objektů Jazyk C# má zajímavou konstrukci pro zamykání objektů na principu monitorů, slouží k tomu konstrukt lock. V jazyku C++/CLI je k dispozici třída lock deklarovaná v souboru <msclr/lock.h>, která poskytuje stejnou funkcionalitu. Reference 1. Sivakumar N. C++/CLI in Action. Manning, 2006. ISBN: 1-932394-81-8. 2. Visual Studio 2005 C++ Language Reference. http://msdn2.microsoft.com/enus/library/3bstk3k5.aspx Annotation: How s and Why s of Combining C++ and.net Platform The paper describes C++/CLI, a language which aims to connect C++ and.net platform. We cover both how s and why s topics, the elements of language are described based on the reader s knowledge of C++ and C#. 16