Sdílet prostřednictvím


Deklarace objektu referenční třídy CLR

Syntaxe pro deklaraci a vytvoření instance objektu typu referenční třídy se od Spravovaných rozšíření jazyka C++ do Visual C++ 2010 změnilo.

Ve spravovaných rozšířeních je objekt referenční třídy deklarován použitím syntaxe ukazatele ISO-C++ s nepovinným použitím klíčového slova __gc vlevo od hvězdičky (*). Zde jsou například různé deklarace objektů typu referenční třídy podle syntaxe spravovaných rozšíření:

public __gc class Form1 : public System::Windows::Forms::Form {
private:
   System::ComponentModel::Container __gc *components;
   Button __gc *button1;
   DataGrid __gc *myDataGrid;   
   DataSet __gc *myDataSet;

   void PrintValues( Array* myArr ) {
      System::Collections::IEnumerator* myEnumerator = 
         myArr->GetEnumerator();

      Array *localArray;
      myArr->Copy(myArr, localArray, myArr->Length);
   }
};

Podle nové syntaxe deklarujete objekt typu referenční třídy pomocí nového deklarativního tokenu (^), formálně označovaného jako sledovací popisovač a neformálně jako stříška. (Adjektivum sledovací znamená, že je odkazový typ umístěn v haldě CLR a proto může transparentně přesouvat umístění během komprimace haldy uvolňování paměti. Sledovací popisovač je za běhu transparentně aktualizován. Dvě podobné koncepce jsou sledovací reference (%) a vnitřní ukazatel (interior_ptr<>), diskutované ve Sémantika typu hodnoty.

Hlavními důvody pro odsunutí deklarativní syntaxe z opětovného použití syntaxe ukazatelů ISO-C++ jsou následující:

  • Použití syntaxe ukazatelů neumožňuje přímou aplikaci přetížených operátorů na objekt odkazu. Místo toho musel být operátor volán pomocí jeho interního názvu, například rV1->op_Addition(rV2) namísto intuitivnějšího rV1+rV2.

  • Množství operací ukazatele, jako je přetypování a aritmetika ukazatele, není povoleno pro objekty uložené na haldě uvolnění paměti. Představa sledovacího popisovače lépe zachycuje povahu odkazového typu CLR.

Modifikátor __gc na sledovacím popisovači je nepotřebný a není podporován. Použití samotného objektu se nemění; stále přistupuje ke členům prostřednictvím operátoru volby člena ukazatele (->). Zde je například předchozí ukázka kódu spravovaných rozšíření přeložená do nové syntaxe:

public ref class Form1: public System::Windows::Forms::Form {
private:
   System::ComponentModel::Container^ components;
   Button^ button1;
   DataGrid^ myDataGrid;
   DataSet^ myDataSet;

   void PrintValues( Array^ myArr ) {
      System::Collections::IEnumerator^ myEnumerator =
         myArr->GetEnumerator();

      Array ^localArray;
      myArr->Copy(myArr, localArray, myArr->Length);   }
};

Dynamické přidělování objektu na haldě CLR

Ve spravovaných rozšířeních byla existence dvou výrazů new pro přidělení mezi nativní a spravovanou haldou z velké části transparentní. V téměř všech instancích je kompilátor schopen použít kontext pro určení, zda přidělit paměť z nativní nebo spravované haldy. Příklad:

Button *button1 = new Button; // OK: managed heap
int *pi1 = new int;           // OK: native heap
Int32 *pi2 = new Int32;       // OK: managed heap

Nechcete-li použít přidělování haldy na základě kontextu, můžete nasměrovat kompilátor buď klíčovým slovem __gc nebo __nogc. V nové syntaxi je explicitní oddělená povaha dvou nových výrazů zavedením klíčového slova gcnew. Například předchozí tři deklarace vypadají v nové syntaxi takto:

Button^ button1 = gcnew Button;        // OK: managed heap
int * pi1 = new int;                   // OK: native heap
Int32^ pi2 = gcnew Int32; // OK: managed heap

Zde je inicializace spravovaných rozšíření členů Form1, deklarovaných v předchozích oddílech:

void InitializeComponent() {
   components = new System::ComponentModel::Container();
   button1 = new System::Windows::Forms::Button();
   myDataGrid = new DataGrid();

   button1->Click += 
      new System::EventHandler(this, &Form1::button1_Click);
}

Zde je stejná inicializace přepracována do nové syntaxe. Všimněte si, že stříška není požadována pro odkazový typ, když je cílem výrazu gcnew.

void InitializeComponent() {
   components = gcnew System::ComponentModel::Container;
   button1 = gcnew System::Windows::Forms::Button;
   myDataGrid = gcnew DataGrid;

   button1->Click += 
      gcnew System::EventHandler( this, &Form1::button1_Click );
}

Sledovací odkaz na žádný objekt

V nové syntaxi již 0 nepředstavuje adresu null, ale je považována za integer, stejně jako 1, 10 nebo 100. Hodnotu null pro sledovací odkaz představuje nový speciální token. Například ve spravovaných rozšířeních inicializujeme odkazový typ pro adresování žádného objektu takto:

// OK: we set obj to refer to no object
Object * obj = 0;

// Error: no implicit boxing
Object * obj2 = 1;

V nové syntaxi vyvolá jakákoli inicializace nebo přiřazení typu hodnoty Object implicitní zabalení tohoto typu hodnoty. V nové syntaxi jsou jak obj tak obj2 inicializovány na adresované zabalené objekty Int32, obsahující hodnoty 0 a 1 v tomto pořadí. Příklad:

// causes the implicit boxing of both 0 and 1
Object ^ obj = 0;
Object ^ obj2 = 1;

Proto chcete-li provést explicitní inicializaci, přiřazení a porovnání sledovacího popisovače s hodnotou null, použijte nové klíčové slovo nullptr. Správná podoba původního příkladu vypadá takto:

// OK: we set obj to refer to no object
Object ^ obj = nullptr;

// OK: we initialize obj2 to a Int32^
Object ^ obj2 = 1;

To poněkud ztěžuje portování existujícího kódu do nové syntaxe. Zvažte například následující deklaraci třídy hodnoty:

__value struct Holder {
   Holder( Continuation* c, Sexpr* v ) {
      cont = c;
      value = v;
      args = 0;
      env = 0;
   }

private:
   Continuation* cont;
   Sexpr * value;
   Environment* env;
   Sexpr * args __gc [];
};

Zde jsou jak args tak env odkazové typy CLR. Inicializace těchto dvou členů na 0 v konstruktoru nemůže zůstat při přechodu na novou syntaxi beze změny. Místo toho musí být změněny na nullptr:

value struct Holder {
   Holder( Continuation^ c, Sexpr^ v )
   {
      cont = c;
      value = v;
      args = nullptr;
      env = nullptr;
   }

private:
   Continuation^ cont;
   Sexpr^ value;
   Environment^ env;
   array<Sexpr^>^ args;
};

Podobně testování těchto členů při jejich porovnání s 0 musí být také změněno na porovnání členů s nullptr. Zde je syntaxe spravovaných rozšíření:

Sexpr * Loop (Sexpr* input) {
   value = 0;
   Holder holder = Interpret(this, input, env);

   while (holder.cont != 0) {
      if (holder.env != 0) {
         holder=Interpret(holder.cont,holder.value,holder.env);
      }
      else if (holder.args != 0) {
         holder = 
         holder.value->closure()->
         apply(holder.cont,holder.args);
      }
   }

   return value;
}

Zde je podoba, nahrazující každou instanci 0 hodnotou nullptr. Nástroj pro překládání pomáhá v této transformaci automatizací mnoha nebo všech výskytů, včetně využívání makra NULL.

Sexpr ^ Loop (Sexpr^ input) {
   value = nullptr;
   Holder holder = Interpret(this, input, env);

   while ( holder.cont != nullptr ) {
      if ( holder.env != nullptr ) {
         holder=Interpret(holder.cont,holder.value,holder.env);
      }
      else if (holder.args != nullptr ) {
         holder = 
         holder.value->closure()->
         apply(holder.cont,holder.args);
      }
   }

   return value;
}

nullptr je převeden na libovolný ukazatel nebo typ sledovacího popisovače, ale není povýšen na integrální typ. Například v následující sadě inicializací je nullptr platný pouze jako počáteční hodnota pro první dvě.

// OK: we set obj and pstr to refer to no object
Object^ obj = nullptr;
char*   pstr = nullptr; // 0 would also work here

// Error: no conversion of nullptr to 0 …
int ival = nullptr;

Podobně sada přetížených metod, jako je následující:

void f( Object^ ); // (1)
void f( char* );   // (2)
void f( int );     // (3)

Vyvolání s literálem nullptr, jako je například následující,

// Error: ambiguous: matches (1) and (2)
f(  nullptr );

je dvojznačné, protože nullptr odpovídá jak sledovacímu popisovači tak i ukazateli a neexistuje žádná priorita jednoho typu před jiným. (Tato situace vyžaduje explicitní přetypování, aby nevznikla dvojznačnost.)

Vyvolání s 0 přesně odpovídá instanci (3):

// OK: matches (3)
f( 0 );

protože 0 je typu integer. Nebýt f(int) k dispozici, volání by jednoznačně odpovídalo f(char*) prostřednictvím standardního převodu. Pravidla pro porovnávání dávají přednost přesné shodě před standardním převodem. V případě neexistence přesné shody má přednost standardní převod před implicitním zabalením typu hodnoty. To je důvod, proč nedochází k nejednoznačnosti.

Viz také

Odkaz

Classes and Structs (Managed)

^ (Handle to Object on Managed Heap)

nullptr

Koncepty

Typy spravovaných (C + +/ CL)