enum class x //Alternativas a static_cast?

Iniciado por @XSStringManolo, 7 Junio 2019, 03:05 AM

0 Miembros y 1 Visitante están viendo este tema.

@XSStringManolo

Estoy portando un juego de consola que hice hace tiempo a C++.

Estoy cambiando los enum por enum class del C++11 para poder añadir miembros con los mismos nombres y hacer el codigo más legible.

El problema es que acabo de cambiar casi todos los if else por switch, añadir menus etc. Asique ahora tengo que poner unos 300 static_cast a int, unsigned int, unsigned short int, etc.

Tengo que buscar todo el rato donde estan definidos todos los tipos de datos y es un horror de copia y pega. A parte el codigo ocupa más.

Hay alguna otra manera?

Se puede usar type aliases para que los static_cast<unsigned long long>(PersonajeEnMapa::Personaje) no me ocupen tanto en el código? Tengo varios static_cast en if y case y es una locura xD

Qué se suele hacer?

Loretz

Yo creo que la idea de las enum class (scoped enumeration) es hacer que el código sea más seguro (reducir la probabilidad de bugs) al usar valores simbólicos (los enumerators), pero que ya no representan directamente números como antes.

Quiero decir, si se tiene enum class color {rojo, verde, azul}; queda claro que al decir "rojo" se está diciendo "color::rojo", y ya no, como era antes, el int 0.

Si lo que necesitas es una colección de constantes enteras, probablemente te convenga seguir usando las enum del viejo estilo, o quizá definir las constantes en algún namespace.


@XSStringManolo

El problema es que quiero usar color::rojo
coche::rojo
moto::rojo
tractor::rojo
...

Precisamente lo uso para eso. Para que al hacer rojo++ no me sume +1 a todos los rojo, solo a coche::rojo.

Son constantes pero utilizo otras variables para trabajar con las constantes, por eso necesito usar static_cast.

Tengo codigo así:

Código (cpp) [Seleccionar]
WORD opciones = LOWORD(wParam);
while (MantenerseEnBucle) {
if(HIWORD(wParam) == BN_CLICKED)
{
     if(opciones > static_cast<unsigned short int>(coche::rojo) && opciones <=static_cast<unsigned short int>(moto::rojo) )
      {
           do{
              if(opciones...
              {
                       switch (opciones2)
                       {
                        case static_cast<int>(coche::verde): {estadisticasopcionAnterior = 's';}
                        break;

                       case etc:
                       {
                       if(opciones > static_cast<unsigned short int>(coche::verde) && opciones <=static_cast<unsigned short int>(moto::rojo)           
             } while (estadisticasOpciones.size() != cadenaDeNombres)
}
}

               
Tengo muchísimas sentencias parecidas y muchas bastante más largas. Qué puedo hacer para acortar el codigo. Como no mire el codigo diariamente en un semana tengo que empezar de 0 xD
Ya estoy cometiendo fallos el programa me compila y me funciona mal en algunos trozos y ando como loco a comentar lineas.

RayR

Como los enum class tienen tipado fuerte, no hay forma de evitar los cast. El problema en ese código es que estás usándolos para algo para lo cual no fueron creados. La idea es tener una lista de valores posibles agrupados en un sólo tipo. De hecho, uno de los principales objetivos para crear los enum class y de las principales ventajas de usarlos es precisamente que no se pueden mezclar ni comparar con ningún otro tipo. Lo que hace ese código es, por así decirlo, totalmente contrario a su naturaleza. Te aconsejaría replantear el diseño. Si de cualquier forma lo quieres hacer así, vas a tener que seguir poniendo todos esos static_cast.

@XSStringManolo

No tenía ni idea. Intentaré rediseñarlo, aunque no sé como . Sabes si se puede hacer type aliases de un static_cast o una sentencia compleja? Nunca lo uso. Siempre uso los tipos de datos predefinidos por el std. Nunca los cambio. He visto ejemplos de vectores y strings con type aliases hacia tipos de dstos de otros tamaños. Por eso me pregunto si es correcto y posible reducir las líneas de código de static_cast mediante type alisases reduciendo por ejemplo todo un if a una simple palabra como condicion para aquellos casos en los que repito la misma condicion.

RayR

El problema es que static_cast no devuelve un tipo sino un valor, por eso no se puede hacer type alias. Se me ocurre que podrías ahorrarte algo de trabajo con una función template constexpr (esto último para que funcione con el switch):

Código (cpp) [Seleccionar]
template<typename TipoRetorno, typename TipoOrigen>
constexpr TipoRetorno Convierte(const TipoRetorno& dest, TipoOrigen valor)
{
    return static_cast<TipoRetorno>(valor);
}
...
     if(opciones > Convierte(opciones, coche::rojo) && opciones <= Convierte(opciones, moto::rojo) )
      {
           do{
              if(opciones...
              {
                       switch (opciones2)
                       {
                        case Convierte(opciones2, coche::verde): {estadisticasopcionAnterior = 's';}


Básicamente, Convierte(A, B) significa: convierte el valor de B al tipo de A. No lo probé, pero debería funcionar. No es una gran mejora respecto a lo que estás haciendo, pero te ahorrarías estar teniendo que buscar y poner los tipos de las variables, ya que aquí la función las deduce. Por eso el parámetro dest: no se usa en la función, pero está ahí para que deduzca automáticamente su tipo y no tengas que escribirlo. Sólo habría que tener cuidado, ya que sería muy fácil poner opciones2 donde debías poner opciones, y así...

Aún así, si quieres seguir usando enum, quizás sería una mejor idea volver a los enum simples y ponerles a los valores nombres tipo coche_rojo, moto_rojo. Creo que no perderías mucha legibilidad y te evitarías totalmente las conversiones.

En fin, te dejo estas opciones que creo que son las más simples para tu problema.

@XSStringManolo

Muchísimas gracias de nuevo. Lo hago más que nada para aprender cosas nuevas. Si no, definiria las variables aparte y guardaría los resultados de static_cast en otra variable distinta para hacer la comparación en el if más legible. Solo quería conocer alternativas por si había una más fancy, ya que hago todos los programas usando lo más simple de C++ , a pesar de conocer otras características avanzadas no las uso nunca para nada, solo cuando me obligan las librerías externas.
Por ejemplo las funciones las hago todas void o bool, nunca uso punteros explicitamente si no es para estructuras, etc.

Al final me queda el código más largo pero también más fácil de entender, y como no hago cosas complicadas ni muy grandes, no necesito una eficiencia exigente.
Me gustaría mejorar mis hábitos pero libros a cerca de eso no tengo.
Siempre hago programas a base de funciones, variables, bucles y control de flujo.
Lo máximo que uso es clases para no escribir mucho código, pero como el programa sea pequeño o de tamaño medio lo hago todo a base de bucles while y condicionales xD

No sé si me podrías dar algún consejo a cerca de esto. Se para que se usan las característocas pero no me encuentro con problemas que me fuerzen a usarlas como única opción. Por eso ando usando enum class xD Para que no se me olviden las cosas por no usarlas.

RayR

A mí tampoco me gusta usar cosas complicadas a menos que sea necesario. Cuando uno empieza en esto, a veces quiere hacer todo muy fancy, como dices, pero es absurdo hacerlo cuando una solución más simple sirve. Claro, a veces se justifica usar cosas complejas, por ejemplo, por cuestiones de rendimiento, pero usarlas sólo porque sí es una de las formas más fáciles de identificar a alguien que quiere aparentar saber más de lo que en realidad sabe  :P .

Por eso, soy de los muchos (y cada vez somos más) que piensan que los miembros del comité de estandarización de C++ están perdiendo el rumbo y cada vez se desconectan más de la realidad. Es decir, de pronto agregan cosas útiles, pero asimismo agregan/cambian muchas que al final nadie en el mundo real termina utilizando, y que lo único que ocasionan es complicar el lenguaje, incluso para quienes no las usan  :-\. De hecho, más de algún miembro y ex miembro lo ha reconocido. Pero esto es tema para otra ocasión...

Mi consejo (y ojalá todos los libros y los profesores lo enseñaran) es seguir haciendo programas, cada vez más completos. Al empezar es importante tener un buen manual, para no andar a ciegas, e ir practicando todo lo que se va aprendiendo. Luego hay que aprender a usar APIs externas, por ejemplo, para GUI, programación gráfica, bases de datos, redes, etc. según nos interese. Una vez que hemos practicado y hecho muchos programas sencillos, la única forma de dar el siguiente paso es intentar hacer programas completos. Ojo con esto, que si seguimos haciendo programitas de juguete, como los ejercicios de manuales y cursos, nunca vamos a avanzar. Lo que hay que hacer es programas útiles, aunque sean pequeños, y tratar de que tengan calidad "comercial", es decir, que estén terminados y sean lo más a prueba de errores posible. Por ejemplo, un programa de modo texto que guarde datos de empleados en archivos sólo sirve como ejercicio de práctica. En lugar de eso, tratar de hacer algo como un software de punto de venta con GUI y bases de datos. Al desarrollarlo, imaginar que lo vamos a vender. Intentar pensar en todas las funciones que una tienda podría necesitar. Incluso podemos fijarnos, aunque sea a grandes rasgos, cómo funcionan los de lugares como Walmart y replicarlo. Otra opción podría ser un programa de mensajería instantánea. Empezar con textos, luego implementar la función de enviar emojis, quizás sonidos que se reproduzcan al recibirlos, y finalmente archivos en general. Algo que yo hice en su momento fue un programa para diseñar tarjetas de presentación. Simplemente permitía poner y mover textos, imágenes, y finalmente, guardarlas o imprimirlas. Muy sencillo y básico, pero realmente lo importante aquí es que estén terminados y funcionen bien. Los videojuegos también pueden ser una opción interesante. Se puede hacer algo sencillo, tipo Space Invaders, Pacman, Mario Bros., etc., pero hay que terminarlos: pantalla de inicio, opciones, pantalla de Game Over, quizás guardar/cargar partida. Puede ser sólo un nivel pero que sea completo. Que al menos ese nivel tenga implementado TODO lo que haría un juego comercial. También es importante probar y probar el programa, hacer cosas absurdas, meterle datos erróneos, esforzarnos por hacer que el falle, y cuando lo haga, solucionarlo, hasta que tengamos algo que, aunque sencillo, se parezca a un producto comercial finalizado. Porque es muy común empezar algo, y cuando termina la parte "divertida" y más sencilla, darlo por terminado y engañarnos diciendo que si qusiéramos lo podríamos acabar fácilmente, pero no tenemos tiempo. Y es que ahí, al darle los toques finales, al perfeccionarlo, es donde más aprendemos. Ahí nos encontramos con cosas que no sabemos cómo resolver y nos obligan a buscar información que de otra forma ni siquiera se nos hubieran pasado por la mente.

Después de hacer unos cuantos programas completos, una buena forma de poner a prueba el conocimiento es probar a leer formatos reales. Por ejemplo, intentar hacer un lector/visualizador de imágenes básico, pero programando todo el código de apertura de archivo, sin bibliotecas para eso. El formato BMP es muy sencillo y está bien documentado. El PNG tampoco es tan complicado, y lo "mejor": su documentación deja mucho que desear. Es un estándar, por lo que la especificación está disponible de forma gratuita. De hecho, si no me falla la memoria (hice un lector hace más de dos años), para implementarlo hay que leer 3 especificaciones distintas, cada una peor explicada que la anterior  ;D. Y es que así suelen ser casi todas las especificaciones oficiales, muy mal redactadas, pero aprender a descifrarlas es una habilidad útil. Igualmente importante, los formatos "reales" fueron creados no para facilitarle la vida a un estudiante sino para ser útiles y eficientes, por lo que implementarlos no es trivial. Es una muy buena práctica.

En fin, en el remoto caso de que hayas leído hasta aquí, ése es el mejor consejo que puedo darte.

@XSStringManolo

En el remoto caso de leerlo todo no, si pregunto es porque me interesa de verdad la respuesta, no para hacer el tonto.

Si quisiera aparentar algo me pondría a descargar códigos y cambiarle los nombres de inglés a español y esas gilipollezes. Si quiero usar cosas que no suelo usar es para que no se me olviden, no para aparentar xD

Te tomo el consejo al 100%. El consejo de hacer los programas con enfoque comercial nunca me lo había planteado, siempre hago cosas para mi. Y como las hago para mi, hago chapuzas útiles con errores sin solucionar que sé que no voy a cometer porque conozco el funcionamiento del programa.
Cosas como un array de 8 números en vez de un vector, etc.
Entonces como no tengo en la mente que otros usuarios vaian a usar el programa lo dejo con 200 errores, no le implemento más cosas que serían prácticas con el programa porque yo no las voy a usar, ni me mato a hacerle una gui.

Me gustaría acercarme más a mis objetivos con la programación. Hacer servidores con una interfaz muy sencilla, práctica y agradable/cómoda. Y hacer un rpg en 2D con C++ y alguna librería gráfica.

Lo del servidor en QT me da a mi que con los ejemplos que tienen y tal, podría hacerlo en 1 mes sin saber nada de QT mirando por libros, ejemplos, etc, dedicándole muchas horas al día.
Quiero hacer primero un cliente servidor en Windows api, por eso ando ahora con esa api a tope. Pero de momento no tengo ni la menor idea del tiempo que tardaré en conseguirlo. Ya me empiezo a manejar con ventanas/botones. Cuando me maneje algo mas iré a por sockets, que hay muchos ejemplos y seguro que con la ayuda de los pros del foro y los ejemplos que hay por ahí lo consigo hacer de 0 sin mirar por ningún sitio. (Tras intentarlo 200 veces seguidas? Jaja)

Por otro lado lo del rpg 2D en SDL me da a mi que van a ser años intentándo hacer algo en SDL y mandándolo a la *****, hasta que por cabezonería lo acabe sacando. Tampoco busco hacer gráficos ahí todo exagerados, un super motor etc. Con que 12 pixeles se muevan por mapas de 100... xD Le tengo demasiado respeto a la librería para pretender más. XD




RayR

Creo que mi mensaje da a entender lo contrario a lo que quise. Eso me pasa por escribir en la noche... Disculpa si parecía que te criticaba. Al contrario. Lo de "en el remoto caso" pretendía ser una crítica mi propio post, que, como de costumbre, me quedó mucho más largo de lo que hubiera querido, pero no se me da muy bien resumir.

En lo de aparentar tampoco me refería a ti. Pensaba en algunos programadores, por ejemplo, de stackoverflow, a los que más que ayudar lo que les interesa es presumir. Por ejemplo, alguien dice que apenas inicia en C++ y necesita ayuda para un programa que convierta divisas usando doubles... y le dan una complejísima función con templates, que funciona con double, int, y hasta números romanos y cadenas de texto tipo "doscientos cincuenta", las cuales la función parsea por medio de expresiones regulares  ;D. Todo eso cuando el usuario (que dejó bien claro que era principiante) sólo necesitaba convertir un p$% double. Lo peor es que gente así a veces termina haciendo software comercial o freeware u open source, y por eso tenemos programas lentos y llenos de errores. En fin, lo que quería dar a entender es que eso que decías de que normalmente haces las cosas simples no tiene nada de malo. Sí es útil aprender las cosas complejas, por si un día las necesitas, pero no te preocupes demasiado en querer aprenderlo todo.  Nadie usa ni necesita todo lo que un lenguaje ofrece. Es mucho más útil y meritorio conocer bien un cierto porcentaje del lenguaje (y de su biblioteca estándar) y saber hacer cosas útiles con APIs de terceros, que aprender de memoria todos los algoritmos y contenedores de la STL (incluso las que nadie en el mundo real usa) pero ser incapaz de mostrar una ventana con un botón, o enviar un mensaje de texto por la red, o cualquier otra cosa que no sea parte del lenguaje (y como seguro ya has visto, con el puro C++ estándar no se llega muy lejos). Yo, por ejemplo, no estoy muy actualizado a lo más moderno de C++. Específicamente, no he profundizado mucho en C++17, simplemente porque hay mil cosas más interesantes y productivas que hacer con mi tiempo. Creo que del post anterior queda claro que no soy precisamente fan de todas las decisiones que últimamente toman quienes deciden la evolución del lenguaje.

Lo del enfoque comercial te lo digo porque intentar hacer un programa casi libre de fallos, y sobre todo, terminado, es mucho más difícil de lo que parece, y se aprende mucho haciéndolo. Pero eso lo puedes dejar para cuando tengas más experiencia con las APIs que estás aprendiendo. Creo que hace poco empezaste con la API de Windows, así que lo mejor es empezar con cosas no tan complicadas. La calculadora es un muy buen ejercicio. Es algo útil (no en vano casi todos los SO incluyen una), relativamente sencillo y seguro que te sirve para aprender en general cómo funcionan las aplicaciones de Windows, los eventos, etc. Pero cuando llegues a un punto en que sientas que manejas bien las GUI, sockets, o lo que sea que te interese, el siguiente paso para aprender bien, bien las APIs es hacer aplicaciones completas, aunque sean pequeñas.