Sdílet prostřednictvím


Sémantika typu hodnoty

Sémantika typu hodnoty se od spravovaných rozšíření jazyka C++ do Visual C++ 2010 změnila.

Zde je kanonický jednoduchý typ hodnoty, používaný ve specifikaci spravovaných rozšíření jazyka C++:

__value struct V { int i; };
__gc struct R { V vr; };

Ve spravovaných rozšířeních jsme mohli mít čtyři syntaktické varianty typu hodnoty (kde jsou varianty 2 a 3 sémanticky shodné):

V v = { 0 };       // Form (1)
V *pv = 0;         // Form (2) an implicit form of (3)
V __gc *pvgc = 0;  // Form (3)
__box V* pvbx = 0; // Form (4) must be local 

Vyvolání zděděných virtuálních metod

Form (1) je objekt kanonické hodnoty a je přiměřeně dobře pochopitelný s výjimkou toho, kdy se někdo pokusí vyvolat zděděnou virtuální metodu, jako je ToString(). Příklad:

v.ToString(); // error!

Aby bylo možné vyvolat tuto metodu, protože není přepsána ve V, musí mít kompilátor přístup k přidružené virtuální tabulce základní třídy. Protože jsou typy hodnot stavovým úložištěm bez přidruženého ukazatele na jejich virtuální tabulku (vptr), vyžaduje to zabalení v. V návrhu jazyka spravovaných rozšíření není implicitní zabalení podporováno, ale musí být explicitně zadáno programátorem, jako v

__box( v )->ToString(); // Managed Extensions: note the arrow

Primární motiv za tímto návrhem je pedagogický: podkladový mechanismus musí být viditelný programátorovi tak, aby porozuměl "ceně" za neposkytnutí instance v rámci jejího typu hodnoty. Kdyby V obsahovala instanci ToString, nebylo by zabalení nutné.

Lexikální složitost explicitního zabalení objektu, ale ne podkladová cena zabalení samotného, je v nové syntaxi odebrána:

v.ToString(); // new syntax

avšak za cenu případně zavádějícího návrháře tříd, jako za cenu neposkytnutí explicitní instance metody ToString v rámci V. Důvod pro preferování implicitního zabalení je, že zatímco je obvykle pouze jeden návrhář tříd, je neomezený počet uživatelů, z nichž žádný by neměl možnost upravit V pro odstranění pravděpodobně náročného explicitního zabalení.

Kritéria, podle kterých určit, zda poskytnout nebo neposkytnout přepisující instanci ToString v rámci třídy hodnoty by měla být četnost a umístění jejího použití. Pokud je volána jen velmi zřídka, je samozřejmě pouze malá výhoda v její definici. Podobně je-li volána v nenáročných oblastech aplikace, její přidání také nepřidá měřitelný výkon k obecné rychlosti aplikace. Alternativně můžete zachovat sledovací popisovač na zabalenou hodnotu a volání prostřednictvím tohoto popisovače by nevyžadovalo zabalení.

Již neexistuje výchozí konstruktor třídy hodnoty

Další rozdíl s typem hodnoty mezi spravovanými rozšířeními a novou syntaxí je odebrání podpory pro výchozí konstruktor. Důvodem je, že během provádění existují stavy, ve kterých může CLR vytvořit instanci typu hodnoty bez vyvolání přidruženého výchozího konstruktoru. To znamená, že pokus o podporu výchozího konstruktoru pod spravovanými rozšířeními v rámci typu hodnoty by v praxi nemohl být zaručen. Z důvodu této neexistence záruky bylo lepší podporu zcela vynechat, spíše než ji ponechat v aplikaci neurčitelnou.

Toto není tak špatně, jak se mohlo zpočátku zdát. Důvodem je, že každý objekt typu hodnoty je automaticky vynulován (to znamená, že je každý typ inicializován na jeho výchozí hodnotu). V důsledku toho nejsou členové místní instance nikdy nedefinovaní. V tomto smyslu není ztráta schopnosti definovat triviální výchozí konstruktor ve skutečnosti ztrátou – a ve skutečnosti je výkonnější při vykonávání pomocí CLR.

Problém je, když uživatel spravovaných rozšíření definuje netriviální výchozí konstruktor. Toto nemá žádné mapování na novou syntaxi. Kód v konstruktoru bude muset být přenesen do pojmenované inicializační metody, která by potom musela být explicitně vyvolána uživatelem.

Deklarace objektu typu hodnoty je jinak v rámci nové syntaxe beze změny. Špatnou stránkou tohoto je, že typy hodnot nejsou uspokojivé pro obalování nativních typů z následujících důvodů:

  • Není k dispozici podpora destruktoru v rámci typu hodnoty. To znamená, že neexistuje způsob jak automatizovat sadu akcí, které jsou spouštěné na konci doby života objektu.

  • Nativní třída může být obsažena pouze v rámci spravovaného typu jako ukazatel, který je pak alokován na nativní haldě.

Chtěli bychom obalit malou nativní třídu v typu hodnoty, raději než odkazový typ, k zamezení dvojité alokace haldy: nativní halda k udržování nativního typu a halda CLR k udržování spravované obálky. Obalení nativní třídy v rámci typu hodnoty vám umožňuje vyhnout se spravované haldě, ale neposkytuje žádný způsob automatizace zpětného získávání paměti nativní haldy. Odkazové typy jsou jediné proveditelné spravované typy, v rámci kterých obalit netriviální nativní třídy.

Vnitřní ukazatele

Form (2) a Form (3) výše mohou adresovat téměř vše v tomto světě nebo další (to znamená, vše spravované nebo nativní). Tak například všechny následující jsou povoleny ve spravovaných rozšířeních:

__value struct V { int i; };
__gc struct R { V vr; };

V v = { 0 };  // Form (1)
V *pv = 0;  // Form (2)
V __gc *pvgc = 0;  // Form (3)
__box V* pvbx = 0;  // Form (4)

R* r;

pv = &v;            // address a value type on the stack
pv = __nogc new V;  // address a value type on native heap
pv = pvgc;          // we are not sure what this addresses
pv = pvbx;          // address a boxed value type on managed heap
pv = &r->vr;        // an interior pointer to value type within a
                    //    reference type on the managed heap

Takže V* může adresovat umístění v rámci místního bloku (a proto může volně viset), v globálním rozsahu, v rámci nativní haldy (například, pokud objekt, který adresuje, již byl odstraněn), v rámci haldy CLR (a proto bude sledován v případě, že by měl být přemístěn během uvolnění paměti) a v rámci vnitřku odkazového objektu na haldě CLR (vnitřní ukazatel, jak je nazýván, je také transparentně sledován).

Ve spravovaných rozšířeních neexistuje žádný způsob, jak oddělit nativní aspekty V*; to znamená, že s ním je zacházeno v jeho přístupnosti, která zpracovává možnost jeho adresování objektu nebo podobjektu na spravované haldě.

V nové syntaxi je ukazatel typu hodnoty rozdělen do dvou typů: V*, který je omezen na umístění jiná než na haldě CLR a vnitřní ukazatel interior_ptr<V>, který umožňuje, ale nevyžaduje, adresu v rámci spravované haldy.

// may not address within managed heap 
V *pv = 0; 

// may or may not address within managed heap
interior_ptr<V> pvgc = nullptr; 

Form (2) a Form (3) spravovaných rozšíření se mapují na interior_ptr<V>. Form (4) je sledovací popisovač. Adresuje celý objekt, který byl zabalen v rámci spravované haldy. To je přeloženo v nové syntaxi na V^,

V^ pvbx = nullptr; // __box V* pvbx = 0;  

Následující deklarace ve spravovaných rozšířeních se všechny mapují na vnitřní ukazatele v nové syntaxi. (Jsou typy hodnot v rámci oboru názvů System.)

Int32 *pi;   // => interior_ptr<Int32> pi;
Boolean *pb; // => interior_ptr<Boolean> pb;
E *pe;       // => interior_ptr<E> pe; // Enumeration

Předdefinované typy nejsou považovány za spravované typy, přestože slouží jako aliasy pro typy v rámci oboru názvů System. Následující mapování tedy platí mezi spravovanými rozšířeními a novou syntaxí:

int * pi;     // => int* pi;
int __gc * pi2; // => interior_ptr<int> pi2;

Při překládání V* v existující aplikaci je nejvíce konzervativní strategií vždy přepnutí na interior_ptr<V>. Takto to bylo ošetřeno pod spravovanými rozšířeními. V nové syntaxi má programátor možnost omezení typu hodnoty na adresy jiné než spravované haldy, určením V* raději než vnitřního ukazatele. Pokud při překladu vašeho programu můžete provést přechodné uzavření všech jeho použití a ujištění, že není přiřazena žádná adresa v rámci spravované haldy, pak je ponechání jako V* v pořádku.

Přídavné ukazatele

Uvolnění paměti může volitelně přesunout objekty umístěné na haldě CLR do jiných umístění v rámci haldy, obvykle během fáze komprimace. Tento přesun není žádný problém pro sledovací popisovače, sledovací odkazy a vnitřní ukazatele, které aktualizují tyto entity transparentně. Tento přesun však je problém v případě, že uživatel předal adresu objektu na haldě CLR mimo běhové prostředí. V tomto případě může těkavý přesun objektu způsobit selhání běhového prostředí. Pro osvobození objektů jako jsou tyto od přesouvání je musíme lokálně připnout k jejich umístění v rozsahu jejich vnějšího použití.

Ve spravovaných rozšířeních je deklarován přídavný ukazatel prohlášením deklarace ukazatele za kvalifikovaný pomocí klíčového slova __pin. Zde je mírně upravený příklad ze specifikace spravovaných rozšíření:

__gc struct H { int j; };

int main() 
{
   H * h = new H;
   int __pin * k = & h -> j;
  
   // …
};

V novém návrhu jazyka je přídavný ukazatel deklarován pomocí syntaxe, analogické vnitřnímu ukazateli.

ref struct H
{
public:
   int j;
};

int main()
{
   H^ h = gcnew H;
   pin_ptr<int> k = &h->j;

   // …
}

Přídavný ukazatel pod novou syntaxí je zvláštním případem vnitřního ukazatele. Původní omezení na přídavný ukazatel zůstávají. Například nemůže být použit jako parametr nebo návratový typ metody; může být deklarován pouze na místním objektu. Několik dalších omezení však bylo v nové syntaxi přidáno.

Výchozí hodnotou přídavného ukazatele je nullptr, nikoli 0. pin_ptr<> nelze inicializovat nebo přiřadit 0. Všechna přiřazení 0 v existujícím kódu budou muset být změněna na nullptr.

Přídavnému ukazateli pod spravovanými rozšířeními bylo povoleno adresovat celý objekt, jako v následujícím příkladu převzatém ze specifikace spravovaných rozšíření:

__gc class G {
public:
   void incr(int* pi) { pi += 1; }
};
__gc struct H { int j; };
void f( G * g ) {
   H __pin * pH = new H;   
   g->incr(& pH -> j);   
};

V nové syntaxi není odkazování celého objektu, vráceného výrazem new, podporováno. Namísto toho musí být odkazována adresa vnitřního člena. Příklad:

ref class G {
public:
   void incr(int* pi) { *pi += 1; }
};
ref struct H { int j; };
void f( G^ g ) {
   H ^ph = gcnew H;
   Console::WriteLine(ph->j);
   pin_ptr<int> pj = &ph->j;
   g->incr(  pj );
   Console::WriteLine(ph->j);
}

Viz také

Odkaz

Classes and Structs (Managed)

interior_ptr

pin_ptr

Koncepty

Typy hodnot a jejich chování