A felügyelt típusok elérhetővé teszik a CLR szolgáltatásait, így élvezik annak előnyeit, de ugyanakkor bizonyos megszorítások is vonatkoznak rájuk. Minden felügyelt típus, illetve osztály egyetlen közös ősosztályból származik: az Object-ből.
A globális és lokális változók használatához a CLR támogatja az érték típusokat, amelyeket a __value kulcsszóval definiálunk. Az érték típusú objektumoknak a veremben foglalódik memória. Nem lehet létrehozni őket a new operátorral. A létrehozás csak globális vagy lokális változóként történhet.
A __value kulcsszót nem kell használni a beépített típusoknál, mint pl. int, short, bool, char, stb.
C++-ban a struct és a class szerepe megegyezik, a különbség az alapértelmezett láthatóságban van. Ez az állítás igaz a C++/CLI nyelvre is, mely az érték és referencia típusokat nem struct/class, hanem value/ref kulcsszavakkal különíti el. Mind value class, mind value struct lehetséges. Az érték típusok nem öröklődhetnek, és nem definiálhatunk alapértelmezett konstruktort: minden mező az alapértéket veszi fel, mikor az objektum létrejön.
A beépített típusok természetesen érték típusúak.
Az érték típusú objektumokra a CLR szabályai érvényesek: statikusan tárolódnak, lokális változóként pl. a veremben. Dobozolás lehetséges, lásd lentebb.
Típus | Csomagoló osztály | Min. érték | Max. érték | Leírás |
short | System::Int16 | -32768 | 32767 | 16 bites előjeles egész |
int | System::Int32 | -2 147 483 648 | 2 147 483 647 | 32 bites előjeles egész |
long | System::Int64 | -9 223 372 036 854 775 808 | 9 223 372 036,854 775 807 | 64 bites előjeles egész |
unsigned short | System::UInt16 | 0 | 65535 | 16 bites előjel nélküli egész |
unsigned int | System::UInt32 | 0 | 4 294 967 295 | 32 bites előjel nélküli egész |
unsigned long | System::UInt64 | 0 | 18 446 744 073 709 551 615 | 64 bites előjel nélküli egész |
bool | System::Boolean | false | true | Logikai típus |
__wchar_t | System::Char | 0x0000 | 0xFFFF | Unicode karakter |
unsigned char | System::Byte | -128 | 127 | Előjeles bájt |
char | System::SByte | 0 | 255 | Előjel nélküli bájt |
float | System::Single | -3.402823e38 | 3.402823e38 | Előjeles 32 bites lebegőpontos valós |
double | System::Double | -1.79769313486232e308 | 1.79769313486232e308 | Előjeles 64 bites lebegőpontos valós |
A fenti típusok ezeket az interfészeket implementálják: IComparable (összehasonlítható), IFormattable (formázható), és IConvertible (konvertálható). Mindegy, hogy deklaráláskor a hagyományos C++-beli típuselnevezést használjuk vagy a csomagolóosztály nevét. A csomagolóosztályok különböző hasznos metódusokat tartalmaznak, mint például:
Ezt a típust a C++-ban megszokottak szerint hozzuk létre, annyi különbséggel, hogy a egy plusz __value kulcsszó kerül a struct elé:
Annak ellenére, hogy az érték típusokat nem lehet dinamikusan létrehozni, mégis megtehetjük ezt, ha erre célra a felügyeletlen heapet használjuk. Ezt a __nogc kulcsszóval érhetjük el.
Bár működik, de elveszti a szemétgyűjtés lehetőségét, mivel a C++ dinamikus memóriaterületén jön létre az objektum. Ezért a fenti kód esetén fennáll a memórielszivárgás veszélye. Olyan esetekben szükséges, ha esetleg együtt kell működni a programnak felügyeletlen elemekkel. A new operátor __nogc verziója természetesen nem használható nem érték típusoknál.
Tömbök. Minden felügyelt tömb a System::Array osztályból származik.
Tömbök deklarációja érték típusok esetén eltér a C++ -tól. Általában: Típus Azon __gc[méret]. Felügyeletlen típusok esetén nem kell használjuk a __gc kulcsszót, de olyankor egy felügyeletlen C++ tömböt kapunk. Felügyelt referencia típus esetén nem kötelező a kulcsszó használata.
Példa egy egészeket tartalmazó egydimenziós tömbre:
Többdimenziós tömb esetén is más a szintaktika. Általában: Típus Azon __gc[,,..,],ahol a vesszők száma a dimenziót adja meg.
Az indexelés se egyezik a megszokottal a többdimenziós esetben:
C++/CLI-ben bevezették az array kulcsszót a managed tömbök deklarálásához. A deklaráció formája a template példányosításhoz hasonlít:
array
Példák
Mi van akkor, ha CLR dinamikus memóriaterületére akarunk érték típusú változót tenni? Becsomagolhatjuk egy referencia típusú osztályba vagy struktúrába és kész. De mivel gyakran szükségünk lehet erre, ezért bevezettek egy eljárást amit dobozolásnak (boxingnak) hívnak.
A dobozolás elkészíti egy érték típusú objektum felügyelt másolatát és a CLR heapbe helyezi. Ehhez a __box kulcsszót kell használni, amely bitenként átmásolja az objektumot a felügyelt objektumba, ami System::ValueType típusú (azaz leszármazottja a System::Object -nek). Az új felügyelt objektum címét adja vissza. Fontos: a létrehozott másolaton a változtatások nem hatnak vissza az eredeti érték típusú objektumra.
Példa:
A dobozolás viszonylag drága mulatság, amely C#-ban implicit módon történik, Managed C++-ban explicit jelezni kell ezt a szándékunkat. (Teljesítménybeli megfontolások miatt van így.)
Egy érték típusú osztály dobozolt példányát a __dynamic_cast, vagy a __try_cast kulcsszavak használatával kaphatjuk meg. Kapunk egy mutatót, amiből dereferenciával visszakapjuk az érték típusú objektumot. Ezt hívják kidobozolásnak.
Példa:
A C++/CLI-ben a C#-hoz hasonlóan implicit módon történik a dobozolás. A fontos különbség, hogy a C++/CLI lehetőséget ad arra, hogy a típus információt megtartsuk:
A felügyelt osztályok viselkedése változott a leginkább a C++/CLI-ben a Managed C++-hoz képest. A Managed C++-ban a * operátorral deklarálhatunk referenciákat egy osztályra, és a __gc new operátorral hozhatjuk őket létre a managed heapen. Az objektumokat a szemétgyűjtő kezeli és semmisíti meg. Ha az osztálynak van explicit destruktora, az objektum delete operátorral manuálisan is megsemmisíthető, ez azonban erősen ellenjavallott.
A CLR-ben fontos különbség van az érték és referencia típusú objektumok között: előbbiek statikusan tárolódnak, egy osztály mezőjében, vagy épp a veremben, míg
utóbbiak mindig a heap-en léteznek. Az érték típusú objektumoknak nincs destruktora, a konstruktort is csak inicializálásra használjuk.
Az osztályokat azonban konstruktorokon keresztül hozzuk létre, és adhatunk meg destruktort még .NET-ben is. A destruktor lefutása azonban egyáltalán nem
determinisztikus, a Garbage Collector lehet, hogy lefut amint elhagyjuk az aktuális metódust, de lehet hogy a program végéig egyszer sem. Így ez a módszer nem
használható az erőforrások hatékony kezelésére.
Ahhoz, hogy az erőforrásokat elengedjük, mikor nincs már rájuk szükség, külön metódushívásra van szükség, amit a munka végeztével meghívunk (pl. egy fájlművelet
befejezésekor lezárjuk a fájlt). E célból került a .NET-be az IDisposable interface, melynek Dispose() metódusa pont ezt a feladatot látja el: a "felügyeletlen"
erőforrások felszabadítása. C#-ban ez a rendszer egy új nyelvi elemmel, a using kulcsszóval használható: A using (Objektum) {...} blokk elhagyásakor
az Objektum->Dispose() metódus meghívódik, a legfoglalt erőforrások felszabadulnak.
Managed C++ ilyen nyelvi elemet nem vezettek be, helyette a using<> template használható erre a célra, valami olyasmi módon, mint a C++ intelligens pointerei.
A C++/CLI-ben egy új megközelítést választottak, mely jobban illeszkedik a C++ programozó gondolkodásmódjához: referencia típusú objektumokra két külön hivatkozási
mód létezik:
Az objektum mindkét esetben a heap-en jön létre, azonban az első eset a C++ pointert, az utóbbi a C++ vermet szimulálja.
Handle-n keresztüli létrehozáshoz a gcnew operátort használjuk, az objektum viselkedése a szokásos: létrejön a heapen, a szemétgyűjtő megsemmisíti, ha már nincs rá
hivatkozás.
A C++/CLI-ben megváltozott a destruktorok szerepe:
Látható, hogy a ~ destruktort és a handle nélküli deklarációt használva könnyedén szimulálható a C++ verem működése:
Példa:
A felügyelt osztályok főbb tulajdonságai:
Főbb jellegzetességek | Főbb megszorítások |
|
|
Láthatósági szabályok
Név | Jelentés |
private public | Csak az adott assemblyben elérhető public láthatósággal. |
protected public | Az adott assembly-ben public láthatósági szinten elérhető, az assembly-n kívül csak a származtatott osztályok érhetik el. |
Zárolt (sealed) osztályok. Ezzel megakadályozhatjuk, hogy további osztályok legyenek a zárolt osztályból származtatva taggfüggvényeket is elláthatunk ezzel a kulcsszóval, amely nem engedi, hogy felüldefiniáljuk azt a származtatott osztályban. Csak felügyelt osztályoknál lehet alkalmazni. Interfészekhez nem használható.
Absztrakt (abstract) osztályok. Így olyan osztályt definiálhatunk, amelyet csak további osztályok származtatásával lehet létrehozni. Természetesen nem használható zárolt osztályokhoz, és érték típusú osztályokhoz sem.
Felügyelt interfészek. Olyanok mint az osztályok, csak metódusai teljesen virtuálisak, nem tartalmazhatnak semmilyen megvalósítást. Az interface kulcsszóval definiáljuk az ilyen osztályokat. Lehet bennük felsorolási típus, amely érték típusú (__value enum). Nem kell használni a virtual kulcsszót vagy a = 0 szuffixumot.
Főbb megszorításai: