Alice: ¿Por qué este lugar es MUY extraño?
Dodo: Y porque todos los otros lugares no son muy extraños. Debe haber al menos un lugar MUY extraño.
Entonces, echemos un vistazo al texto de la clase de plantilla BitSet con el objetivo de adaptarlo a los requisitos de MK, las principales áreas de optimización se definieron anteriormente. Por supuesto, puede escribir su propia clase desde cero, pero no desaprovechemos la oportunidad de conocer buenas soluciones, porque la biblioteca STL (que no debe confundirse con spl) se conoce desde hace mucho tiempo y se usa en todas partes. Primero necesita encontrar el código fuente, después de un corto viaje en Internet, acabo de abrir el directorio con mi MinGW y busqué el archivo requerido, que tengo la intención de discutir más a fondo.
Al principio vemos un poco más que una página con derechos de autor y el texto de la licencia, así como una variedad de advertencias, bueno, puede omitir esto, esto es para abogados. A continuación se incluyen y agradezco a los autores: cada uno de ellos viene acompañado de un comentario sobre lo que queremos obtener de cada archivo; todos lo harían, el trabajo del investigador de
Indiana Jones sería más fácil. Luego viene la definición, no soy un fanático de la limpieza del código y no discutiré: déjelos y vea de inmediato la definición del número de bits en la unidad de almacenamiento, que es un largo sin firmar. Continuando por el camino resbaladizo de Defines, estoy buscando mi propia definición de unidad de almacenamiento, que es igual a uint8_fast, aunque entiendo que esto claramente no será suficiente. Por cierto, de inmediato agradezco una vez más a los autores por el estilo: al final del módulo que limpian después de ellos mismos, destruyendo las definiciones internas, ni siquiera esperaba ver algo así en la producción moderna.
Y aquí comienzan los ligeros malentendidos: al principio definen la estructura auxiliar de la estructura struct _Base_bitset (por qué exactamente la estructura, y no la clase, ya que son intercambiables), en la que determinan el tipo de almacenamiento de datos y se especifica de nuevo directamente, lo cambiamos al nuestro. A continuación, se determinan dos instancias de esta estructura auxiliar: para una unidad de almacenamiento (bueno, esto es comprensible, para aumentar la eficiencia del código) y para un ejemplo degenerado sin almacenamiento (probablemente para el manejo de errores), hasta que toquemos este código.
A continuación, encontramos el conjunto de bits de la clase de clase principal (ahora la clase), derivado de la estructura de la plantilla (sí, en C es posible, no está claro por qué hacer esto, pero es posible), en el que el tipo de almacenamiento se determina nuevamente y se cambia al nuestro nuevamente. Pero aquí me preguntaba: por qué el tipo no se heredó de la estructura principal y me sorprendió descubrir (sí, con asombro, las clases de plantilla, esto no es lo que hago en la vida cotidiana) que la herencia de las clases de plantilla es algo diferente de las ordinarias. Es decir, la herencia es exactamente la misma y todas las entidades de los padres están disponibles en la clase hija (espero que nadie me lleve a una clase machista), pero deben indicarse con un nombre completo. Bueno, esto todavía se puede entender, de lo contrario el compilador no adivinará cuál de las opciones instanciadas se aplicará, pero si usa los tipos definidos en la clase, aún debe agregar la palabra clave typename. Esta no es una solución obvia, estaba especialmente satisfecho con los diagnósticos del compilador, lo que significa "tal vez tuviste un tipo de la clase principal" - sí, gracias, capitán, a eso me refería, me alegra que nos entendamos, pero No me está resultando más fácil. Sin embargo, dicho diagnóstico tiene lugar en la versión anterior de GCC, en el mensaje actual se hace más claro "tal vez se olvidó la palabra clave typename". La comprensión de los mensajes de diagnóstico del compilador para las clases de plantilla generalmente es un tema para una canción separada, y esta canción no es para nada alegre.
Entonces, si no queremos usar constantemente la construcción en la clase secundaria
std::_Base_bitset<Nb>::_WordT
(y no queremos, neg?) entonces tenemos tres formas
1) trivial
#define _WordT typename _Base_bitset<_Nb>::_WordT
2) obvio - definición de tipo a nivel global y me gustó más
3) para fanáticos de extraños
typedef typename _Base_bitset<_Nb>::_WordT _WordT;
(sensación persistente de una magnífica muleta cromada). Personalmente, en los tres casos, no estoy satisfecho con la indicación directa de la clase principal, porque la implementación no debería tener en cuenta la cadena de herencia, pero tal vez no entiendo algo.
También hay un método 4)
using _WordT = std::_Base_bitset<Nb>::_WordT;
pero en mi versión del compilador no funciona, por lo que no lo es.
Nota sobre los márgenes (PNP): en el maravilloso libro "C ++ para programadores reales" (lo descargué por error en ese momento, decidiendo que estaba dedicado a C ++ en sistemas en tiempo real) dice sobre el lenguaje "Su elegancia no está en absoluto en simplicidad (las palabras C ++ y la simplicidad cortan el oído con su aparente contradicción), pero en sus capacidades potenciales. Para cada problema feo, hay una especie de modismo inteligente, una finta de lenguaje elegante, gracias a la cual el problema se derrite ante nuestros ojos ". Dado que el libro es realmente maravilloso, significa que la cita anterior es correcta (entiendo que hay una cierta extensión lógica aquí), pero personalmente, desafortunadamente, veo el problema, pero existe una tensión con un lenguaje inteligente.
A pesar de la sensación de desconcierto, el problema está resuelto y puedes disfrutar el resultado, pero no estaba allí. Al cambiar el tamaño del conjunto de prueba de 15 a 5, se produjo un error de compilación completamente inesperado. No, todo está claro, una instanciación no modificada con el parámetro de plantilla 1 funcionó, pero desde el exterior parece muy extraño: cambiamos la constante y el programa deja de compilarse.
Por supuesto, puede modificar esta implementación y otra, pero este enfoque parece una violación grave del principio DRY. Hay posibles soluciones, y hay más de una de ellas 1) la obvia - nuevamente la definición y 2) lo trivial - nuevamente la definición de tipo a nivel global, pero en este caso saldrá del módulo, lo que, sin embargo, lo hace en la implementación en consideración, sobresaliendo de la estructura básica , solo no será necesario escribir el calificador.
Por lo tanto, me inclino por la opción 3) la clase base para determinar el tipo y la herencia de todas las instancias a partir de ella. Además, las entidades de la clase padre están protegidas, nuevamente, para que no sobresalgan. Luego encuentro una propiedad divertida, probablemente C ++ debería ser elogiado por su flexibilidad: para la variante sin almacenamiento, el tipo no es necesario y el lenguaje permite que la implementación se use sin herencia, aunque esto no es particularmente necesario en este caso particular. Inmediatamente descubro un inconveniente más de la biblioteca: en las tres variantes, las operaciones de cálculo del número de la unidad de almacenamiento se establecen cada vez
static size_t _S_whichword
y las máscaras de bits _S_maskbit y son completamente idénticas, también las movemos a la clase base. En este caso, se detecta un código "muerto", el método _S_whichbyte, ni siquiera sé qué hacer, por un lado, las buenas reglas de tono requieren su eliminación, por otro lado, en el caso de una plantilla, esto no afecta el código resultante. Usaré la regla - "no entiendo algo - no toques" y dejaré este método.
En principio, las modificaciones en términos del tipo de almacenamiento se completan y puede comenzar a probar. E inmediatamente descubro una falta de implementación: para la arquitectura MSP430, por alguna razón, se asignan palabras de dos bytes en lugar de bytes. Por supuesto, no palabras dobles, como antes, pero aún estamos luchando por el código mínimo (en todos los sentidos). Resulta que el compilador está seguro de que en esta arquitectura el tipo uint_fast8_t tiene un tamaño de 2, aunque el sistema de comando tiene operaciones con bytes y el compilador mismo las usa por completo. La idea de usar este tipo se ve comprometida y debe establecer el tipo de datos uint8_t directamente. Bueno, si hay una arquitectura en la que el trabajo con bytes no tiene éxito y el tamaño del tipo uint_fast8_t es diferente de 1 (ninguno de los disponibles en el compilador), tendrá que sufrir por la velocidad de todos los demás.
La prueba de la versión corregida muestra su correcto funcionamiento en varias arquitecturas y con diferentes parámetros, pero todavía hay una cuestión con el cálculo de máscaras de bits para MK sin cambios desarrollados, en nuestro caso es MSP430 y AVR. En principio, puede simplemente hacer que la lectura de la máscara de bits de la matriz sea un método para todos los casos, independientemente de la arquitectura MK. La solución funciona bastante bien, en arquitecturas desarrolladas con indexación todo está bien, pero aún habrá una pérdida de tiempo en comparación con los cambios rápidos, pero no quisiera meter los dedos con las palabras "la clase será más rápida, dijo, estamos optimizando él dijo ".
Por lo tanto, necesitamos una implementación diferente para nuestras dos arquitecturas débiles, que difieren de todas las demás en el tamaño del tipo uint_fast16_t: es 2 versus 4 u 8 para las versiones de 64 bits. La compilación condicional no nos ayudará, pero establecer la condición con la esperanza de la optimización no es el camino del samurai, los patrones permanecen. Estoy tratando de crear un método de plantilla de la clase, pero la especialización parcial no está disponible para ello; resulta que debería ser así e incluso encontrar un artículo que diga por qué esta es una buena solución. Honestamente, todavía no entendía por qué esta decisión es buena, lo más probable es que esto se deba a consideraciones religiosas. Sin embargo, no se nos preguntó, y qué queda por hacer: puede hacer una función de plantilla amigable (resultó que es imposible y, por la misma razón, la especialización parcial está prohibida), hay otra solución, parece curiosa: la especialización parcial del método fuera del cuerpo de la clase. Incluso puede crear una función de plantilla separada y acceder a ella desde los métodos de la clase, no sé qué método es más correcto, elegí este. Sí, ella no tiene acceso a las entidades internas de la clase, pero en este caso particular no es necesario, qué hacer si es necesario, todavía no lo sé, "resolveremos los problemas a medida que surjan".
Ahora tenemos todo lo que necesitamos, reunimos los fragmentos probados y obtenemos el código que resuelve el problema de optimización inicial. Al mismo tiempo, hicimos el código más compacto (lo que significa más comprensible, aunque este último es discutible), eliminamos numerosas repeticiones y eliminamos las entidades redundantes que sobresalen. Lo único que no se arregla es una mezcla de estructuras y clases, pero aquí no entiendo las razones de tal implementación, por lo tanto, por si acaso, no tocaré esta parte.
Se dedicará una segunda publicación a la implementación de la segunda versión del conjunto, y sin eso algo ha funcionado mucho.