Compartilhar via


Semântica de tipo de valor

Valor tipo semântica mudou de Managed Extensions for C++ para Visual C++.

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

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

Em gerenciado extensões, podemos ter quatro variações sintáticas de um tipo de valor (onde 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 

Chamar métodos virtuais herdados

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

v.ToString(); // error!

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

__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 ele entenda o 'custo' de não fornecer uma instância do tipo do valor.Foram V para conter uma instância de ToString, o boxe não seria necessário.

A complexidade léxica de boxing explicitamente o objeto, mas não o custo base de boxing, é removida a nova sintaxe:

v.ToString(); // new syntax

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

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

Não é um construtor de padrão de classe de valor

Outra diferença com um tipo de valor entre gerenciado extensões e a nova sintaxe é a remoção do suporte para um construtor padrão.Isso ocorre porque há ocasiões durante a execução em que o CLR pode criar uma instância de tipo de valor sem chamar o construtor padrão associado.Isto é, 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.Dada a ausência de garantia, foi sentido para ser melhor soltar o suporte totalmente em vez de ter ser 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 para 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 em todos os – e na verdade é mais eficiente quando executado pelo CLR.

O problema é quando um usuário do 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 migradas para um método de inicialização nomeado que precisará ser explicitamente chamado 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 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 de um tipo de valor.Ou seja, não é possível automatizar um conjunto de ações disparado pelo fim da vida útil do objeto.

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

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 pilha dupla: nativo heap para armazenar o tipo nativo e o heap CLR para manter wrapper gerenciado.Quebra automática de uma classe nativa dentro de um tipo de valor permite que você evite a heap gerenciada, mas fornece a maneira de 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 nativos classes.

Ponteiros interiores

Form (2)e Form (3) acima pode resolver praticamente qualquer coisa no mundo ou Avançar (ou seja, nada gerenciado ou nativo).Assim, por exemplo, todas as seguintes são permitidas em gerenciado extensões:

__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* podem endereçar um local dentro de um bloco local (e portanto pode ser dangling), no escopo global, o nativo de pilha (por exemplo, se o objeto aborda já tiver sido excluído), no heap CLR (e, portanto, serão controladas se devem ser realocado durante a coleta de lixo) e no interior de um objeto de referência no heap CLR (um interior ponteirocomo é chamado, é também transparente controlado).

Extensões gerenciadas, não há nenhuma maneira de separar os aspectos nativos de um V*; Isto é, ela é tratada em inclusive, que lida com a possibilidade de ele endereçamento um objeto ou subobjeto no heap gerenciado.

Na sintaxe de novo, um ponteiro de tipo de valor é decomposto em dois tipos: V*, que é limitada a locais de pilha não-CLR e o ponteiro interior interior_ptr<V>, que permite mas não requer um endereço dentro da heap gerenciada.

// 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) de extensões gerenciado mapear para interior_ptr<V>.Form (4)é uma alça de controle.Ele aborda todo o objeto que foi in a box no heap gerenciado.É convertida na nova sintaxe em um V^,

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

As seguintes declarações em gerenciado extensões todas mapeiam para ponteiros interiores na nova sintaxe.(Eles são tipos de valor dentro do System namespace.)

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

Tipos internos não são considerados tipos gerenciados, embora eles servem como aliases para os tipos de System namespace.Assim, os seguintes mapeamentos verdadeiras entre gerenciado extensões e a nova sintaxe:

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

Ao traduzir uma 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 endereços de pilha não-gerenciados, especificando V* em vez de um ponteiro interior.Se em traduzir seu programa, você pode fazer um fechamento transitivo de todos os seus usos e verifique se nenhum endereço atribuído está no heap gerenciado, em seguida, deixando-o como V* tudo bem.

Fixação de ponteiros

O coletor de lixo, opcionalmente, pode mover 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 controle de identificadores, referências de controle e ponteiros interiores que atualizar essas entidades de forma transparente.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, nós localmente deve fixá-los para o local para a extensão de seu uso externo.

Em gerenciado extensões, um fixação ponteiro declarado qualificando 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 pinning é declarado com sintaxe semelhante a que um ponteiro interior.

ref struct H
{
public:
   int j;
};

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

   // …
}

Um ponteiro 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 do 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, não 0.A pin_ptr<> não pode ser inicializado 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 extensões gerenciadas foi permitido para tratar de um objeto inteiro, como no exemplo a seguir extraído a 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 sintaxe de novo, fixar o objeto inteiro retornado pela new não há suporte para a expressão.Em vez disso, o endereço do membro interior 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)