A notação de projeção e a introdução de < de > safe_cast
A notação de projeção foi alterado a partir de Managed Extensions for C++ para Visual C++ 2010.
A modificação de uma estrutura existente é uma experiência diferente e mais difícil de criar a estrutura inicial. Há menos graus de liberdade e a solução tende na direção de um compromisso entre uma reestruturação ideal e o que é praticável de acordo com as dependências estruturais existentes.
Extensão de linguagem é outro exemplo. No início da década de 1990 como programação de orientar o objeto se tornou uma paradigma importante, a necessidade de um recurso de lançamento decrescente de segurança de tipos em C++ se tornou urgentes. Baixar é a conversão explícita do usuário de um ponteiro de classe de base ou uma referência de um ponteiro ou de uma classe derivada. Baixar requer uma conversão explícita. A razão é que o tipo real de que o ponteiro da classe base é um aspecto do tempo de execução; o compilador, portanto, não é possível verificá-lo. Ou então, especifique novamente que, um recurso de lançamento decrescente, assim como uma chamada de função virtual requer alguma forma de resolução dinâmica. Isso gera duas perguntas:
Por que um lançamento decrescente seria necessário no paradigma orientado a objeto? Não é o mecanismo de função virtual suficiente? Isto é, por que não é um declaração que qualquer necessidade de um lançamento decrescente (ou uma conversão de qualquer tipo) é uma falha de design?
Por que o suporte de um lançamento decrescente deve ser um problema no C++? Afinal, não é um problema nas linguagens orientadas a objeto, como Smalltalk (ou, posteriormente, Java e C#)? O que é sobre o C++ faz um lançamento decrescente difícil de recurso de suporte?
Uma função virtual representa um algoritmo de dependentes do tipo comum a uma família de tipos. (Estamos não considerando interfaces, que não são compatíveis com ISO C++, mas estão disponíveis na programação do CLR e que representam uma alternativa interessante de design). O design do que família é normalmente representado por uma hierarquia de classe no qual há uma classe base abstrata, declarando a interface comum (funções virtuais) e um conjunto de classes derivadas concretas, que representam os tipos de família reais no domínio do aplicativo.
A Light a hierarquia em um domínio de aplicativo computador gerado fotográficos (CGI), por exemplo, terão os atributos comuns, como color, intensity, position, on, offe assim por diante. Um pode controlar várias luzes, usando a interface comum, sem se preocupar se uma determinada luz é um destaque, uma luz direcional, uma luz de não-direcional (pense o SOL) ou talvez uma luz celeiro-door. Nesse caso, não é necessário baixar para um tipo específico de luz para exercitar a sua interface virtual. No entanto, em um ambiente de produção, velocidade é essencial. Um talvez precise e explicitamente chamar cada método se fazendo isso in-line a execução das chamadas pode ser executada em vez de usar o mecanismo de virtual.
Portanto, é um motivo para lançamento decrescente em C++ para suprimir o mecanismo virtual por um ganho significativo no desempenho em tempo de execução. (Observe que a automação dessa otimização manual é uma área ativa de pesquisa. No entanto, é mais difícil de resolver que substituir o uso explícito da register ou inline palavra-chave.)
Uma segunda razão precise fica fora da natureza dupla do polimorfismo. Uma maneira de pensar polimorfismo está sendo dividida em um par de passivo e dinâmico de formulários.
Uma chamada virtual (e um recurso de lançamento decrescente) representam dinâmica usa polimorfismo: um está realizando uma ação com base no tipo real de que o ponteiro de classe base nessa instância em particular na execução do programa.
No entanto, a atribuição de um objeto de classe derivada para seu ponteiro de classe base é uma forma de passiva de polimorfismo; ele está usando o polimorfismo como um mecanismo de transporte. Este é o principal uso do Object, por exemplo, programação CLR pre-generic. Quando usado passivamente, o ponteiro de classe base escolhido para o transporte e armazenamento normalmente oferece uma interface que é muito abstrata. Object, por exemplo, fornece aproximadamente cinco métodos através de sua interface; qualquer comportamento mais específico requer um explícito lançamento decrescente. Por exemplo, se quisermos ajustar o ângulo do nosso destaque ou a sua taxa de descaindo, teríamos que precise explicitamente. Uma interface virtual dentro de uma família de subtipos practicably não pode ser um superconjunto de todos os métodos possíveis de seus muitos filhos e, assim, um recurso que precise sempre serão necessários dentro de uma linguagem orientada a objeto.
Se um cofre precise facility é necessária em uma linguagem orientada a objeto, então por que ele levou C++ tão longa para adicionar um? O problema está em como disponibilizar as informações de como o tipo de tempo de execução do ponteiro. No caso de uma função virtual, as informações de tempo de execução é configurar em duas partes pelo compilador:
O objeto de classe contém um membro de ponteiro de tabela virtual adicional (seja no início ou fim do objeto de classe; Isso tem um histórico de interessante em si) que aborda a tabela virtual apropriada. Por exemplo, um objeto de destaque aborda uma tabela virtual de destaque, uma luz direcional, uma tabela de virtual de luz direcional e assim por diante
Cada função virtual possui um tipo fixo slot na tabela e invocar a instância real é representada pelo endereço armazenado dentro da tabela. Por exemplo, o virtual Light destruidor pode estar associado a slot 0, Color com o slot 1 e assim por diante. Esta é uma estratégia eficiente de se inflexíveis, porque ele está configurado no momento da compilação e representa uma sobrecarga mínima.
O problema, então, é como disponibilizar as informações de tipo do ponteiro sem alterar o tamanho dos ponteiros de C++, adicionando um segundo endereço ou adicionando diretamente a algum tipo de codificação tipo. Isso não seria aceitável para os programadores (e programas) que decidir que não usam o paradigma orientado a objeto – ainda era a comunidade de usuários predominante. Outra possibilidade foi apresentar um ponteiro especial para os tipos de classe polimórfico, mas isso seria ser confuso e dificultar a inter-mix os dois, principalmente com problemas de aritmética de ponteiro. Ela também não seria aceitável para manter uma tabela em tempo de execução que associa cada ponteiro seu tipo atualmente associado e atualizar dinamicamente.
Em seguida, o problema é um par de comunidades de usuários que possuem diferentes mas legítimos aspirations de programação. A solução deve ser um compromisso entre as duas comunidades, permitindo que cada não apenas seu aspiration, mas a capacidade de interoperar. Isso significa que as soluções oferecidas por ambos os lados têm probabilidade de ser inviável e a solução implementada finalmente ser menor do que é perfeito. A resolução real gira em torno de definição de uma classe polimórfica: uma classe polimórfica é aquele que contém uma função virtual. Uma classe polimórfica suporta um dinâmico type-safe lançamento decrescente. Isso resolve o problema de manter-o--como-endereço do ponteiro, porque todas as classes polimórficas contenham esse membro de ponteiro adicional para sua tabela virtual associada. As informações de tipo associado, portanto, podem ser armazenadas em uma estrutura de tabela de virtual expandido. O custo de type-safe precise é (quase) localizado para usuários do recurso.
A próxima edição com o lançamento decrescente type-safe era sua sintaxe. Porque é uma projeção, a proposta original para o Comitê de ISO C++ usado a sintaxe acrescidos de elenco, como no exemplo:
spot = ( SpotLight* ) plight;
mas isso foi rejeitado pelo comitê porque ele não permitiu que o usuário controle o custo do tom. Se o dynamic type-safe precise tem a mesma sintaxe que anteriormente não seguro, mas estático notação de projeção, em seguida, ele se torna uma substituição e o usuário tem a capacidade de suprimir a sobrecarga de runtime quando é desnecessário e talvez muito caro.
Em geral, no C++, sempre há um mecanismo pelo qual suprimir a funcionalidade de suporte do compilador. Por exemplo, podemos desativá-la o mecanismo virtual usando o operador de escopo de classe (Box::rotate(angle)) ou chamando o método virtual por meio de um objeto de classe (em vez de um ponteiro ou uma referência de classe). Este último supressão não é necessária para o idioma, mas é uma qualidade de um problema de implementação, semelhante a supressão da construção de um temporário em uma declaração do formulário:
// compilers are free to optimize away the temporary
X x = X::X( 10 );
Portanto, a proposta foi levada novamente para fazer mais considerações e várias notações alternativas foram consideradas e aquele trazidas de volta para o comitê foi do formulário (?type), qual indicado indeterminado – ou seja, dinâmicas natureza. Isso fornecia ao usuário a capacidade de alternar entre as duas formas – estáticas ou dinâmicas – mas ninguém estava muito satisfeito com ele. Então, era voltar para a placa de desenho. A notação de terceira e bem-sucedida é o agora padrão dynamic_cast<type>, que foi generalizada para um conjunto de quatro notações de conversão do novo estilo.
ISO c++, dynamic_cast retorna 0 quando aplicado a um tipo de ponteiro inadequados e lança um std::bad_cast exceção quando aplicado a um tipo de referência. No Managed Extensions for C++, aplicando dynamic_cast a um tipo de referência gerenciada (por causa de sua representação de ponteiro) sempre retornado 0. __try_cast<type>foi introduzido como um análogo à exceção lançando variante a dynamic_cast, exceto que ele lança System::InvalidCastException se falhar a conversão.
public __gc class ItemVerb;
public __gc class ItemVerbCollection {
public:
ItemVerb *EnsureVerbArray() [] {
return __try_cast<ItemVerb *[]>
(verbList->ToArray(__typeof(ItemVerb *)));
}
};
Na nova sintaxe, __try_cast tem sido recast como safe_cast. Eis o fragmento de código mesmo a nova sintaxe:
public ref class ItemVerb;
public ref class ItemVerbCollection {
public:
array<ItemVerb^>^ EnsureVerbArray() {
return safe_cast<array<ItemVerb^>^>
( verbList->ToArray( ItemVerb::typeid ));
}
};
No mundo gerenciado, é importante permitir código verificável, limitando a capacidade de programadores converter entre tipos de maneiras que deixe o código não verificado. Este é um aspecto crucial do paradigma de programação dinâmico representado pela nova sintaxe. Por esse motivo, instâncias de conversões de estilo antigo são recast internamente como projeções de tempo de execução, portanto, que, por exemplo:
// internally recast into the
// equivalent safe_cast expression above
( array<ItemVerb^>^ ) verbList->ToArray( ItemVerb::typeid );
Por outro lado, porque o polimorfismo fornece um ativo e um modo passivo, às vezes, é necessário executar um lançamento decrescente apenas para acessar a API não-virtual de um subtipo. Isso pode ocorrer, por exemplo, com os membros de uma classe que desejam resolver qualquer digite dentro da hierarquia (passivo polimorfismo como um mecanismo de transporte), mas para o qual a instância real em um contexto de programa específico é conhecida. Nesse caso, a necessidade de uma verificação de tempo de execução do tom pode ser uma sobrecarga inaceitável. Se a nova sintaxe servir como os linguagem de programação de sistemas gerenciados, ele deverá fornecer alguns meios de permitir um tempo de compilação (isto é, estáticas) lançamento decrescente. É por isso que a aplicação do static_cast notação é permitida para permanecer um tempo de compilação precise:
// ok: cast performed at compile-time.
// No run-time check for type correctness
static_cast< array<ItemVerb^>^>(verbList->ToArray(ItemVerb::typeid));
O problema é que não há nenhuma maneira de garantir que o programador fazendo o static_cast está correto e bem-intencionados; ou seja, não há nenhuma maneira de forçar o código gerenciado para que seja verificável. Isso é uma preocupação mais urgente sob o paradigma do programa dinâmico que sob nativo, mas não é suficiente dentro de um sistema a capacidade de alternar entre um estático e a projeção de tempo de execução de linguagem para impedir que o usuário de programação.
Há uma interceptação de desempenho e a armadilha na nova sintaxe, entretanto. Na programação nativa, não há nenhuma diferença no desempenho entre a notação de projeção de estilo antigo e o novo estilo static_cast notação. Mas a nova sintaxe, a notação de estilo antigo cast é significativamente mais cara do que o uso do novo estilo static_cast notação. O motivo é que o compilador transforma internamente o uso da notação de estilo antigo em uma verificação de tempo de execução lança uma exceção. Além disso, ele também altera o perfil de execução do código porque ele faz com que uma exceção não tratada, trazendo o aplicativo – talvez com sabedoria, mas o mesmo erro não causaria essa exceção se a static_cast notação foram usados. Alguém pode argumentar que isso ajudará a prod os usuários em usando a notação de estilo novo. Mas somente quando ele falhar; Caso contrário, ele fará com que programas que usam a notação de estilo antigo significativamente lento sem uma compreensão visível do motivo, semelhantes às seguintes armadilhas do programador de C:
// pitfall # 1:
// initialization can remove a temporary class object,
// assignment cannot
Matrix m;
m = another_matrix;
// pitfall # 2: declaration of class objects far from their use
Matrix m( 2000, 2000 ), n( 2000, 2000 );
if ( ! mumble ) return;