C++/CLI és Managed C++

Felügyelt típusok

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.

Érték típusok

Managed C++

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++/CLI

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.

A beépített típusok:

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:

Struktúrák - Managed C++

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é:

__value struct ExampleValueType {}; ExampleValueType globalVar; int main() { ExampleValueType localVar; }

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.

void Func() { ExampleValueType* pVar = __nogc new ExampleValueType; }

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.

Referencia típusok

Tömbök. Minden felügyelt tömb a System::Array osztályból származik.

Managed C++

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:

int vIntegers __gc[]; vIntegers = new int __gc[10];

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.

int vIntegers2 __gc[,,]; vIntegers = new int __gc[10,5,3];

Az indexelés se egyezik a megszokottal a többdimenziós esetben:

int n = vIntegers2[1,2,3]; //nem pedig vIntegers[1][2][3]

C++/CLI

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

array vIntegers; vIntegers = gcnew array(100); array vStringMatrix; vStringMatrix = gcnew array(10,10);

Dobozolás

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.

Managed C++

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:

__value struct V { int i; }; void Positive( Object* ) { } // egy felügyelt osztályt vár int main() { V v = { 10 }; // lefoglalás és inicializálás __box V* pBoxedV = __box( v ); // másolás a CLR heapbe Positive( pBoxedV ); // itt már felügyelt osztályként kezeli pBoxedV->i = 20; // a dobozolt verzió módosítása return 0; }

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:

__value struct V { int i; }; int main() { V v = { 10 }; Object* o = __box( v ); // az érték másolása a CLR heapbe V v2 = *dynamic_cast<__box V*>( o ); // visszamásolás a CLR heapből }

C++/CLI

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:

int i = 123; // value int^ hi = i; // boxing int c = *hi; // unboxing hi = nullptr; // null handle

Felügyelt osztályok, interfészek, erőforráskezelés

Managed C++

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.

C++/CLI

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:

SqlConnection connection("Database=master; Integrated Security=sspi"); SqlCommand^ command = connection.CreateCommand(); command->CommandText = "sp_databases"; command->CommandType = CommandType::StoredProcedure; connection.Open(); SqlDataReader reader(command->ExecuteReader()); while (reader.Read()) { Console::WriteLine(reader.GetString(0)); }

A felügyelt osztályok főbb tulajdonságai:

Főbb jellegzetességek Főbb megszorítások
  • Lehet olyan adattagja ami felügyeletlen típusú objektumra mutató pointer.
  • Lehet felhasználó által definiált destruktora.
  • Megvalósíthat bármennyi felügyelt interfészt (interface).
  • Lehetnek tulajdonságai (property).
  • Megjelölhetjük az abstract kulcsszóval.
  • Az osztályt zárolhatjuk (sealed).
  • Lehet statikus konstruktora.
  • Lehet konstruktora.
  • Lehetnek láthatósági szabályzói (public, protected, private).
  • Nem származtatható felügyeletlen osztályból.
  • Felügyeletlen osztály nem származtatható belőle.
  • Legfeljebb egy(!) felügyelt osztályból származtatható.
  • Nem lehet felhasználó által írt copy konstruktora.
  • Nem deklarálhat vagy definiálhat friend osztályokat vagy függvényeket.
  • Nem deklarálhat vagy definiálhat new vagy delete operátort.
  • Nem tartalmazhat using deklarációt.
  • Nem lehet konstans (const) tagfüggvénye.
  • Ha nem adjuk meg az ősosztályát, akkor a rendszer úgy veszi, hogy a System::Object-ból származik.

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: