Alice: Por que este lugar é um lugar MUITO estranho?
Dodo: E porque todos os outros lugares não são muito estranhos. Deve haver pelo menos um lugar MUITO estranho.
Então, vamos dar uma olhada no texto da classe de modelo BitSet com o objetivo de adaptá-lo aos requisitos do MK, as principais áreas de otimização foram definidas anteriormente. É claro que você pode escrever sua própria classe do zero, mas não vamos negligenciar a oportunidade de se familiarizar com boas soluções, porque a biblioteca STL (que não deve ser confundida com spl) é conhecida há muito tempo e é usada em todos os lugares. Primeiro, você precisa encontrar o código-fonte. Após uma curta viagem à Internet, eu apenas abri o diretório com o meu MinGW e procurei o arquivo necessário, que pretendo discutir mais.
No começo, vemos um pouco mais do que uma página com direitos autorais e o texto da licença, bem como uma variedade de avisos - bem, você pode pular isso, isto é para advogados. A seguir, incluem-se e agradeço aos autores - cada um é acompanhado por um comentário sobre o que queremos obter de cada arquivo - todo mundo faria isso, o trabalho do pesquisador de
Indiana Jones seria mais fácil. As definições vêm a seguir, não sou um fanático pela limpeza de códigos e não discutirei - deixe-as ver imediatamente a definição do número de bits na unidade de armazenamento, que tem um longo período sem assinatura. Continuando no caminho escorregadio de Defines, estou procurando minha própria definição de unidade de armazenamento, que é igual a uint8_fast, embora eu entenda que isso claramente não será suficiente. A propósito, imediatamente trago mais um agradecimento aos autores pelo estilo - no final do módulo, eles limpam depois de si mesmos, destruindo definições internas - nem esperava que eu visse algo assim na produção moderna.
E aqui começam os mal-entendidos - inicialmente, eles definem a estrutura auxiliar do modelo struct _Base_bitset (por que exatamente a estrutura, e não a classe, uma vez que são intercambiáveis), na qual eles determinam o tipo de armazenamento de dados e são especificados novamente diretamente - mudamos para o nosso. Em seguida, duas instâncias dessa estrutura auxiliar são determinadas - para uma unidade de armazenamento (bem, isso é compreensível, para aumentar a eficiência do código) e para um exemplo degenerado sem armazenamento (provavelmente para manipulação de erros), até tocarmos nesse código.
A seguir, encontramos o conjunto de bits da classe principal (agora a classe), derivado da estrutura do modelo (sim, em C é possível, não está claro por que fazer isso, mas é possível), no qual o tipo de armazenamento é determinado novamente e alterado para o nosso novamente. Mas aqui me perguntei - por que o tipo não foi herdado da estrutura pai e ficou surpreso ao descobrir (sim, com espanto, classes de modelo - isso não é o que eu faço na vida cotidiana) que a herança de classes de modelo é um pouco diferente das classes comuns. Ou seja, a herança é exatamente a mesma e todas as entidades dos pais estão disponíveis na classe filha (espero que ninguém me leve para um chauvinista de gênero), mas elas devem ser indicadas com um nome completo. Bem, isso ainda pode ser entendido; caso contrário, o compilador não adivinhará quais das opções instanciadas aplicar, mas se você usar os tipos definidos na classe, ainda deverá adicionar a palavra-chave typename. Esta não é uma solução óbvia, fiquei particularmente satisfeito com o diagnóstico do compilador, o que significa "talvez você tenha um tipo da classe dos pais" - sim, obrigado, capitão, é isso que eu tinha em mente, fico feliz que nos entendamos, mas não está ficando mais fácil para mim. No entanto, esse diagnóstico ocorre na versão antiga do GCC, na mensagem atual fica mais claro "talvez você tenha esquecido a palavra-chave typename". Compreender as mensagens de diagnóstico do compilador para as classes de modelo geralmente é um tópico para uma música separada, e essa música não é de forma alguma prazerosa.
Portanto, se não queremos usar constantemente o construto na classe filho
std::_Base_bitset<Nb>::_WordT
(e não queremos, neg?), então temos três maneiras
1) trivial
#define _WordT typename _Base_bitset<_Nb>::_WordT
2) óbvio - definição de tipo em nível global e eu gostei mais
3) para fãs de estranhos
typedef typename _Base_bitset<_Nb>::_WordT _WordT;
(sensação persistente de uma magnífica muleta cromada). Pessoalmente, nos três casos, não estou satisfeito com a indicação direta da classe pai, porque a implementação não deve levar em consideração a cadeia de herança, mas talvez eu não entenda alguma coisa.
Existe também um método 4)
using _WordT = std::_Base_bitset<Nb>::_WordT;
mas na minha versão do compilador não funciona, por isso não é.
Nota sobre as margens (PNP) - no maravilhoso livro “C ++ para programadores reais” (baixei-o por engano na época, decidindo que era dedicado ao C ++ em sistemas em tempo real), ele diz sobre a linguagem “Sua elegância não é de forma alguma simplicidade (as palavras C ++ e simplicidade cortam a orelha com sua aparente contradição), mas em suas potencialidades. Para todo problema feio, existe algum tipo de idioma inteligente, uma finta linguagem elegante, graças à qual o problema derrete bem diante de nossos olhos. ” Como o livro é realmente maravilhoso, significa que a citação acima está correta (eu entendo que há um certo trecho lógico aqui), mas pessoalmente, infelizmente, vejo o problema, mas há uma tensão com um idioma inteligente.
Apesar da sensação remanescente de perplexidade, o problema está resolvido e você pode apreciar o resultado - mas não estava lá. Ao redimensionar o conjunto de testes de 15 para 5, ocorreu um erro de compilação completamente inesperado. Não, está tudo claro, uma instanciação não modificada com o parâmetro 1 do modelo funcionou, mas do lado de fora parece muito estranho - alteramos a constante e o programa para de compilar.
Obviamente, você pode modificar essa implementação e outra, mas essa abordagem parece uma violação grave do princípio DRY. Existem soluções possíveis, e há mais de uma delas 1) a óbvia - novamente a define e 2) a trivial - novamente a definição de tipo no nível global, mas, neste caso, ela ficará fora do módulo, o que, no entanto, ocorre na implementação em consideração, saindo da estrutura básica , mas também o qualificador não precisará ser gravado.
Portanto, estou inclinado à opção 3) a classe base para determinar o tipo e a herança de todas as instâncias dela. Além disso, as entidades da classe pai são protegidas, novamente, para que não se destacem. Então descubro uma propriedade engraçada, provavelmente o C ++ deve ser elogiado por sua flexibilidade - para uma variante sem armazenamento, o tipo não é necessário e a linguagem permite que a implementação seja usada sem herança, embora isso não seja particularmente necessário neste caso específico. Descobri imediatamente mais uma desvantagem da biblioteca - nas três variantes, as operações de cálculo do número da unidade de armazenamento são definidas sempre
static size_t _S_whichword
e as máscaras nele _S_maskbit e elas são completamente idênticas - também as movemos para a classe base. Nesse caso, um código "morto" é detectado - o método _S_whichbyte, eu nem sei o que fazer - por um lado, as regras de bom tom exigem sua remoção, por outro lado - no caso de um modelo, isso não afeta o código resultante. Vou usar a regra - "não entendo alguma coisa - não toque" e deixo esse método.
Em princípio, as modificações em termos do tipo de armazenamento são concluídas e você pode começar a testar. E imediatamente descubro uma falta de implementação - para a arquitetura MSP430, por algum motivo, palavras de dois bytes são alocadas em vez de bytes. Obviamente, não palavras duplas, como antes, mas ainda estamos lutando pelo código mínimo (em todos os sentidos). Acontece que o compilador tem certeza de que nessa arquitetura o tipo uint_fast8_t tem tamanho 2, embora o sistema de comando tenha operações com bytes e o próprio compilador as use completamente. A idéia de usar esse tipo está comprometida e você deve definir o tipo de dados uint8_t diretamente. Bem, se houver uma arquitetura na qual o trabalho com bytes não seja bem-sucedido e o tamanho do tipo uint_fast8_t for diferente de 1 (nenhum dos disponíveis no compilador), ele terá que sofrer com a velocidade de todos os outros.
O teste da versão corrigida mostra sua operação correta em várias arquiteturas e com parâmetros diferentes, mas ainda resta o problema de calcular máscaras de bits para MK sem mudanças desenvolvidas, no nosso caso são MSP430 e AVR. Em princípio, você pode simplesmente tornar a leitura da máscara de bits da matriz um método para todos os casos, independentemente da arquitetura MK. A solução está funcionando bem, em arquiteturas desenvolvidas com indexação está tudo bem, mas ainda haverá uma perda de tempo em relação às mudanças rápidas, mas eu não gostaria de cutucar meus dedos com as palavras “a turma será mais rápida, ele disse, estamos otimizando ele disse. "
Portanto, precisamos de uma implementação diferente para nossas duas arquiteturas fracas, que diferem de todas as outras no tamanho do tipo uint_fast16_t - é 2 versus 4 ou 8 para versões de 64 bits. A compilação condicional não nos ajudará, mas definir a condição na esperança de otimização não é o caminho do samurai, os padrões permanecem. Estou tentando criar um método de modelo da classe, mas a especialização parcial não está disponível para ele - acontece que deve ser assim e até encontra um artigo que diz por que essa é uma boa solução. Honestamente, eu ainda não entendi por que essa decisão é boa, provavelmente porque isso se deve a considerações religiosas. No entanto, não nos perguntaram, e o que resta a ser feito - você pode fazer uma função de modelo amigável (acabou sendo impossível, e pelo mesmo motivo - a especialização parcial é proibida), existe outra solução, parece engraçado - a especialização parcial do método fora do corpo da classe. Você pode até criar uma função de modelo separada e acessá-la a partir dos métodos da classe. Não sei qual é o método mais correto, escolhi este. Sim, ela não tem acesso às entidades internas da turma, mas nesse caso em particular não é necessário, o que fazer se necessário, ainda não sei: "resolveremos os problemas à medida que surgirem".
Agora temos tudo o que precisamos, reunimos os fragmentos testados e obtemos o código que resolve o problema de otimização inicial. Ao mesmo tempo, tornamos o código mais compacto (o que significa mais compreensível, embora o último seja discutível), removemos inúmeras repetições e eliminamos entidades redundantes. A única coisa que não é corrigida é uma mistura de estruturas e classes, mas aqui eu não entendo os motivos de tal implementação, portanto, por precaução, não tocarei nesta parte.
Um segundo post será dedicado à implementação da segunda versão do conjunto, e sem isso algo deu certo.