Partilhar via


Semântica do tipo de valor

A semântica do tipo de valor foi alterada de extensões gerenciadas para C++ a Visual C++.

Aqui está o tipo de valor simples canônico usado nas extensões gerenciadas para especificações C++:.

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

Em extensões gerenciadas, podemos ter quatro variantes sintáticas de um tipo de valor (onde os formulários 2 e 3 são os mesmos semanticamente):

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 

Invocando métodos herdados virtuais

Form (1) é o objeto canônico do valor, e é composta por razoavelmente boa, exceto quando alguém tenta invocar um método virtual herdado como ToString(). Por exemplo:

v.ToString(); // error!

Para invocar esse método, pois não é substituído em V, o compilador deve ter acesso à tabela virtual associado da classe base. Como os tipos de valor são instalou o armazenamento sem o ponteiro associado à sua tabela virtual (vptr), isso requer que v está encaixotado. No design gerenciado de linguagem DMX, o com implícita não tem suporte mas deve ser especificado explicitamente pelo programador, como em

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

O motriz primário por trás desse projeto é pedagógico: o mecanismo subjacente precisa ser visível ao programador de modo que compreende os custos de “” não fornecer uma instância do tipo de valor. Foi V para conter uma instância do com ToString, não será necessário.

A complexidade léxica explicitamente de uso de maiúsculas e minúsculas no objeto, mas não o custo de subjacentes com próprio, é descartada na nova sintaxe:

v.ToString(); // new syntax

mas à custa possivelmente enganar do designer da classe quanto aos custos de ter fornecido uma instância explícita do método de ToString dentro de V. A razão para preferir o com implícita é que normalmente quando há apenas um designer da classe, há um número ilimitado de usuários, nenhum dos quais teria a liberdade para alterar V para eliminar a caixa explícita possivelmente ingrato.

Os critérios pelos quais para determinar se fornecer uma instância de ToString substituindo em uma classe do valor deve ser a frequência e o local dos seus usos. Se for chamada muito raramente, não há pouco benefício em sua definição. Da mesma forma, se for chamada em áreas non-performant do aplicativo, adicionando o também não adicionará mensuràvel o desempenho geral do aplicativo. Como alternativa, se possível manter um identificador de rastreamento para o valor encaixotado, e as chamadas naquela identificador não exigem o com.

Não há mais de um construtor padrão da classe do valor

Outra diferença com um tipo de valor entre extensões gerenciadas e a nova sintaxe é a remoção de suporte para um construtor padrão. Isso é porque há ocasiões durante a execução em que CLR é possível criar uma instância do tipo de valor sem invocar o construtor padrão associado. Isto é, a tentativa em extensões gerenciadas do suporte a um construtor padrão em um tipo de valor não pode ser garantida na prática. Considerando que a ausência de garantia, ele esteve sentida para ser melhor remover o suporte completo em vez de ser tiver não determinístico em seu aplicativo.

Isso não é tão incorreto pode inicialmente como exibir. Isso é porque cada objeto de um tipo de valor é zerado out automaticamente (ou seja, cada tipo é inicializado com seu valor padrão). No resultado, os membros de uma instância local do nunca são indefinidos. Nesse sentido, a perda da capacidade de definir um construtor padrão trivial não é realmente uma perda de qualquer – e não é de fato mais eficiente quando executado por CLR.

O problema é quando um usuário de extensões gerenciadas define um construtor padrão não trivial. Isto não tem nenhum mapeamento para a nova sintaxe. O código no construtor precisará ser migrado em um método chamado de inicialização que precise depois de ser chamado explicitamente pelo usuário.

A declaração de um objeto de tipo de valor na nova sintaxe é normalmente inalterado. O para baixo lateral dos tipos de valores não são satisfatórios para o encapsulamento de tipos nativos para as seguintes razões:

  • Não há suporte para um destruidor dentro de um tipo de valor. Isto é, não há como automatizar um conjunto de ações disparadas para o fim do tempo de vida de um objeto.

  • Uma classe contida nativo pode ser somente dentro de um tipo gerenciado como um ponteiro que é então atribuído no heap nativo.

Nós gostaríamos de envolver uma classe nativo pequena em um tipo de valor em vez de um tipo de referência para evitar uma alocação de dois heap: o heap nativo para manter o tipo nativo, e o heap de CLR para manter o wrapper gerenciado. Envolver uma classe nativo em um tipo de valor permite que você evite o heap gerenciados, mas não oferece nenhuma forma de automatizar a recuperação de memória nativo do heap. Os tipos de referência são o único tipo gerenciado executável dentro dos quais quebrar classes nativas não triviais.

Ponteiros interiores

Form (2) e Form (3) acima podem tratar praticamente qualquer coisa no mundo ou no seguinte (ou seja, qualquer coisa gerenciado ou nativo). Assim, por exemplo, os seguintes são permitidos em extensões gerenciadas:

__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

Assim, V* pode suportar um local dentro de um bloco local (e pode oscilar) em virtude disso, no escopo global, no heap nativo (por exemplo, se o objeto que trata já tiver sido excluído), no heap de CLR (e em virtude disso será acompanhado se realocado durante a coleta de lixo), e no interior de um objeto de referência no heap de CLR (um ponteiro interior, como isso é chamado, o erro é também transparente).

Em extensões gerenciadas, não há nenhuma forma de separar os aspectos nativos de V*; ou seja, é tratada em seu inclusivo, que trata a possibilidade delas que trata de um objeto ou um subobject no heap gerenciado.

Na nova sintaxe, um ponteiro de tipo de valor é fatorado em dois tipos: V*, que é limitado aos locais de heap de CLR, e não o ponteiro interior, interior_ptr<V>, que permite mas não requer um endereço no heap gerenciado.

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

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

Form (2) e Form (3) do mapa gerenciado das extensões em interior_ptr<V>. Form (4) é um identificador de rastreamento. Resolve o objeto inteiro que foi encaixotado no heap gerenciado. É convertida na nova sintaxe em V^,

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

As seguintes instruções em extensões gerenciadas todo o mapa para armazenar ponteiros interiores na nova sintaxe. (São tipos de valor no namespace de System .)

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

Os tipos internos não são considerados tipos gerenciados, embora servam como aliases para tipos no namespace de System . Para os seguintes mapeamentos mantêm true entre extensões gerenciadas e a nova sintaxe:

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

Ao converter V* em seu programa existente, a estratégia mais conservadora é gerencie-lo sempre para interior_ptr<V>. Isso é tratado como foi em extensões gerenciadas. Na nova sintaxe, o programador tem a opção de restringir um tipo de valor para endereços não gerenciado do heap especificando V* em vez de um ponteiro interior. Se, ao converter seu programa, você pode fazer um fechamento transitivo de todos os seus usos e para ter a certeza de que nenhum endereço está atribuído no heap gerenciado, então deixando o V* como é bem.

Fixando ponteiros

O coletor de lixo pode opcionalmente mover os objetos que residem no heap de CLR para locais diferentes no heap, normalmente durante a fase de consolidação. Este mover não é um problema para controlar os identificadores, o rastreamento referências, e os ponteiros interiores que atualizam essas entidades transparente. Mover esse é um problema, entretanto, se o usuário tiver passado o endereço de um objeto no heap de CLR fora do ambiente de tempo de execução. Nesse caso, a movimentação do objeto temporário é prováveis que causam uma falha de tempo de execução. Para isentar objetos como estes de ser movido, devemos fixar-los localmente em seu local para a extensão do uso da parte externa.

Em extensões gerenciadas, um ponteiro fixando-se é declarado qualificadas uma declaração de ponteiro com a palavra-chave de __pin . Aqui está um exemplo ligeiramente modificada da especificação gerenciado de extensões:

__gc struct H { int j; };

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

No novo design de idioma, um ponteiro fixando-se for declarado com a sintaxe análoga a de um ponteiro interior.

ref struct H
{
public:
   int j;
};

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

   // …
}

Um ponteiro fixando-se na nova sintaxe é um caso especial de um ponteiro interior. As restrições originais em um ponteiro fixando-se permanecem. Por exemplo, não pode ser usado como um parâmetro ou tipo de retorno de um método; pode ser declarado apenas em um objeto local. Um número de restrições adicionais, porém, foram adicionadas na nova sintaxe.

O valor padrão de um ponteiro fixando-se é nullptr, não 0. pin_ptr<> não pode ser inicializado ou 0atribuído. Todas as atribuições de 0 no código existente precisarão ser alteradas a nullptr.

Um ponteiro fixando-se em extensões gerenciadas era permitido para endereçar um objeto inteiro, como no exemplo a seguir extraído da especificação gerenciado de extensões:

__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);   
};

Na nova sintaxe, fixar o objeto inteiro retornado pela expressão de new não tem suporte. Em vez disso, o endereço do membro dentro precisa ser fixado. Por exemplo,

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);
}

Consulte também

Referência

Classes e Estruturas (Extensões de Componentes C++)

interior_ptr (C++/CLI)

pin_ptr (C++/CLI)

Conceitos

Tipos de valor e seus comportamentos (C++/CLI)