Compartilhar via


Semântica do tipo de valor

Valor de tipo semântica foram alterados a partir de Managed Extensions for C++ para Visual C++ 2010.

Este é o tipo de valor simples canônico usado a Managed Extensions for C++ especificações de:

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

Extensões gerenciadas, podemos pode ter quatro variantes sintático de um tipo de valor (onde formulários 2 e 3 são as mesmas 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 

Chamar métodos virtuais herdados

Form (1)é o objeto de valor canônico e é razoavelmente bem entendido, exceto quando alguém tenta chamar um método virtual herdado, como ToString(). For example:

v.ToString(); // error!

Para invocar esse método, porque ele não for substituído em V, o compilador deve ter acesso à tabela virtual associada da classe de base. Como os tipos de valor estão no estado armazenamento sem o ponteiro associado à sua tabela virtual (vptr), isso requer que v ser in a box. No design de linguagem de Managed Extensions, boxing implícita não é suportado mas deve ser especificado explicitamente pelo programador, como em

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

A principal motivação por trás desse projeto é pedagógico: mecanismo de base deve ser visível para o programador para que ela compreendam o custo não fornecer uma instância do tipo do valor. Foram V para conter uma instância de ToString, a conversão boxing não seria necessário.

A complexidade lexical de boxing explicitamente o objeto, mas não o custo subjacente de boxing propriamente dito, é removida a nova sintaxe:

v.ToString(); // new syntax

mas o custo da possivelmente equivocado o class designer como para o custo de não ter fornecido a uma instância explícita da ToString método dentro do V. O motivo de boxing implícito a preferência é que, embora geralmente seja apenas uma class designer, há um número ilimitado de usuários, nenhum dos quais terá a liberdade de modificar V para eliminar a caixa explícita possivelmente dispendioso.

Os critérios pelos quais determinar se deve ou não fornecer uma instância de substituição de ToString dentro de um valor que classe deve ser a freqüência e a localização de usos. Se for chamado muito raramente, é claro que há poucas vantagens em sua definição. Da mesma forma, se ele for chamado em áreas de não-desempenho do aplicativo, adicioná-lo também não concretamente adicionará o desempenho geral do aplicativo. Como alternativa, um pode manter uma alça de controle para o valor do processador in a box e chamadas por meio desse identificador não exigiria boxing.

Não existe mais um construtor padrão do valor de classe

Outra diferença com um tipo de valor entre extensões gerenciadas e a nova sintaxe é a remoção do suporte para um construtor padrão. Isso ocorre porque existem ocasiões durante a execução, na qual o CLR pode criar uma instância do tipo de valor sem chamar o construtor padrão associado. Ou seja, a tentativa de Managed Extensions para oferecer suporte a um construtor padrão dentro de um tipo de valor pode não na prática ser garantida. Devido a ausência de garantia, foi achamos ser melhor soltar o suporte totalmente, em vez de tê-lo não-determinística em seu aplicativo.

Não é tão ruim quanto parece inicialmente. Isso ocorre porque cada objeto de um tipo de valor é zerado automaticamente (isto é, cada tipo é inicializado com o valor padrão). Como resultado, os membros de uma instância local nunca são indefinidos. Nesse sentido, a perda da capacidade de definir um construtor padrão trivial realmente não é uma perda todo – e na verdade, é mais eficiente quando desempenhadas pelo CLR.

O problema é quando um usuário de Managed Extensions define um construtor padrão não é trivial. Isso não tem mapeamento para a nova sintaxe. O código dentro do construtor precisará ser migrados para um método de inicialização nomeado que precisaria ser invocada explicitamente pelo usuário.

Caso contrário, a declaração de um objeto de tipo de valor dentro da nova sintaxe é alterada. O lado isso é que os tipos de valor não são satisfatórios para a disposição dos tipos nativos pelos seguintes motivos:

  • Não há nenhum suporte para um destruidor dentro de um tipo de valor. Ou seja, não há nenhuma maneira de automatizar a um conjunto de ações, disparado pelo fim do tempo de vida do objeto.

  • Uma classe nativa pode estar contida dentro de um tipo gerenciado como um ponteiro para o qual é então alocado no heap nativa.

Gostaríamos de dispor de uma pequena classe nativa em um tipo de valor em vez de um tipo de referência para evitar uma alocação de heap duplo: o heap nativa para conter o tipo nativo e o heap CLR para manter o wrapper gerenciado. Quebra automática de uma classe nativa dentro de um tipo de valor permite que você evite o heap gerenciado, mas não oferece meios para automatizar a recuperação da memória heap nativa. Tipos de referência são apenas praticável de acordo com tipo gerenciado dentro do qual quebrar não triviais classes nativas.

Ponteiros interiores

Form (2)e Form (3) acima pode resolver praticamente qualquer coisa no mundo ou o próximo (ou seja, nada gerenciada ou nativa). Assim, por exemplo, todos os itens a seguir é permitidos no Managed Extensions:

__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

Portanto, um V* pode tratar de um local dentro de um bloco de local (e portanto pode ser dangling), no escopo global, o nativo de pilha (por exemplo, se o objeto que ela atende já tiver sido excluído), dentro do heap CLR (e portanto serão rastreadas se devem ser realocado durante a coleta de lixo) e dentro do interior de um objeto de referência no heap CLR (um interior ponteirocomo isso é chamado também de forma transparente é controlada).

No Managed Extensions, não há nenhuma maneira para separar os aspectos nativos de um V*; ou seja, ele será tratado no inclusive, que lida com a possibilidade de que o endereçamento de um objeto ou objetos filho específicos no heap gerenciado.

Na sintaxe de novo, um ponteiro de tipo de valor é fatorado em dois tipos: V*, que é limitado aos locais de pilha não CLR e o ponteiro de interior, interior_ptr<V>, que permite, mas não requer um endereço dentro do 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 de Managed Extensions para interior_ptr<V>. Form (4)é uma alça de controle. Ele aborda todo o objeto que foi in a box no heap gerenciado. Ele é convertido em nova sintaxe em um V^,

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

As seguintes declarações de Managed Extensions todos mapeiam para o interiores ponteiros na nova sintaxe. (Eles são os tipos de valor dentro do System espaço para nome.)

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 eles servem como aliases para os tipos de dentro do System namespace. Assim, os seguintes mapeamentos verdadeiras entre extensões gerenciadas e a nova sintaxe:

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

Ao traduzir um V* no seu programa existente, a estratégia mais conservadora é sempre ativá-lo um interior_ptr<V>. Isso é como ele foi tratado em extensões gerenciadas. A nova sintaxe, o programador tem a opção de restringir um tipo de valor para os endereços de pilha não-gerenciados, especificando V* em vez de um ponteiro interior. Se, no seu programa de conversão, você pode fazer um fechamento transitivo de todos os seus usos e certifique-se de que nenhum endereço atribuído está dentro do heap gerenciado, em seguida, deixando-o como V* não apresenta problemas.

A fixação de ponteiros

O coletor de lixo pode mover, opcionalmente, os objetos que residem no heap CLR a locais diferentes dentro do heap, geralmente durante a fase de compactação. Essa movimentação não é um problema de ponteiros interiores que atualizar essas entidades de forma transparente, referências de rastreamento e alças de controle. Essa movimentação é um problema, no entanto, se o usuário tiver passado o endereço de um objeto no heap CLR fora do ambiente de tempo de execução. Nesse caso, o movimento volátil do objeto provavelmente causar uma falha de tempo de execução. Para isolar objetos como esses sejam movidas, podemos deve fixá-los localmente para o local para a extensão de seu uso externo.

No Managed Extensions, um a fixação de ponteiro é declarada pela qualificação de uma declaração de ponteiro com o __pin palavra-chave. Aqui está um exemplo ligeiramente modificado da especificação de Managed Extensions:

__gc struct H { int j; };

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

No novo design de linguagem, um ponteiro de fixação é declarado com sintaxe semelhante à de um ponteiro interior.

ref struct H
{
public:
   int j;
};

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

   // …
}

Um ponteiro de fixação sob a nova sintaxe é um caso especial de um ponteiro interior. As restrições originais de um ponteiro de fixação permanecem. Por exemplo, ele não pode ser usado como um parâmetro ou tipo de retorno de um método; Ele pode ser declarado apenas em um objeto local. Um número de restrições adicionais, no entanto, foram adicionado na nova sintaxe.

O valor padrão de um ponteiro de fixação é nullptr, e não 0. A pin_ptr<> não podem ser inicializados ou atribuído 0. Todas as atribuições de 0 no código existente precisará ser alterado para nullptr.

Um ponteiro de fixação em Managed Extensions era permitido para tratar de um objeto inteiro, como no exemplo a seguir, extraído da especificação de Managed Extensions:

__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, fixação de todo o objeto retornado pela new não há suporte para a expressão. Em vez disso, o endereço do membro interior precisa ser fixado. For example,

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 and Structs (Managed)

interior_ptr

pin_ptr

Conceitos

Tipos de valor e seus comportamentos