Cómo pasar de constantes de tiempo de compilación a constantes en tiempo de ejecución

“Usa constantes en el código!” Todo el mundo te lo dice, y tienen razón. El problema comienza cuando te das cuenta de que algunas constantes se pierden en el tiempo de compilación, deben leerse en tiempo de ejecución (después de que el programa comienza), desde un archivo, registro, base de datos, etc.

También pueden surgir problemas mientras estás probando y depurando tu aplicación, o cuando necesitas realizar pruebas de estrés en alguna parte de la misma. Tendrás que ajustar los valores de algunas constantes, y podrías terminar con una gran cantidad de configuraciones (Cuadro A). Incluso peor, intenta perfilar tu aplicación. Tendrás que ejecutarla varias veces con diferentes valores para las constantes, porque cambiar un valor requerirá volver a compilar la aplicación.

En este artículo, te mostraré varias formas de cambiar de constantes de tiempo de compilación a constantes de tiempo de ejecución. El proceso inverso es igual de fácil, y esto es importante, ya que después de perfilar, podrías decidir mantener algunas constantes de tiempo de compilación para que tu aplicación se ejecute más rápido.

Índice de Contenido
  1. Tiempo de ejecución vs. Tiempo de compilación
  2. ¿Qué tan constante es constante?

Tiempo de ejecución vs. Tiempo de compilación

Una constante de tiempo de compilación es un valor que se puede (y se calcula) en tiempo de compilación. Una constante de tiempo de ejecución es un valor que se calcula solo mientras el programa se está ejecutando. Si ejecutas el mismo programa más de una vez:

  • Una constante de tiempo de compilación tendrá el mismo valor cada vez que se ejecute la aplicación.
  • Una constante de tiempo de ejecución puede tener un valor diferente cada vez que se ejecute la aplicación.

Hay que tener en cuenta que una vez inicializadas, ninguno de los tipos de constantes puede cambiar su valor, son constantes. Por ejemplo:
const int TIEMPO_DE_COMPILACION = 5; // establecido en tiempo de compilación; su valor es 5 (cinco)
int leer_int() { int val; std::cin >> val; return val; }
const int TIEMPO_DE_EJECUCION = leer_int(); // establecido en tiempo de ejecución; puede ser cualquier valor

Transformar una constante de tiempo de compilación en una constante de tiempo de ejecución no es tan fácil como parece, ya que no puedes controlar el momento en que se inicializa la constante de tiempo de ejecución. Además, una constante de tiempo de ejecución, una vez inicializada, no debería poder cambiar su valor.

Las funciones de manipulación de cadenas en JavaScript y cómo utilizarlas

¿Qué tan constante es constante?

Hay algunos casos en los que las constantes de tiempo de ejecución simplemente no funcionarán y se requerirán constantes de tiempo de compilación. Saber cuáles son estos casos te ahorrará muchos problemas. Incluso si declaras una variable como constante, si la inicializas solo en tiempo de ejecución, no puedes usarla para:

  • Límites de arreglo
  • Expresiones de caso
  • Longitudes de campos de bits
  • Inicializadores de enumeradores
  • Argumentos nontype de plantillas integrales o de enumeración

La Cuadro A muestra ejemplos de cada uno.

Cuadro A:



Tiempo de compilación
Tiempo de ejecución
(genera error en la compilación)

Límites de arreglo
const int MAX=5;
int a[MAX];

#include <iostream>
//lee un entero en tiempo de ejecución
int leer_en_tiempo_de_ejecucion()
{int n=0; std::cin>>n; return n;}

const int MAX=leer_en_tiempo_de_ejecucion();
int a[MAX];
int main() {}

Expresiones de caso

const int MAX=5

Cómo aplicar patrones de diseño en el desarrollo de software

int i=leer_en_tiempo_de_ejecucion();
switch(i)
{
case MAX:
std::cout<<“leyó el máximo.”<<std::endl;
break;
default:
std::cout<<“leyó”<<i<<std::endl;
break;
}

// leer_en_tiempo_de_ejecucion – igual que antes const int MAX=leer_en_tiempo_de_ejecucion();
switch(i)
{
case MAX:
std::cout<<“leyó el máximo.”<<std:endl; break;
default:
std::cout<<“leyó”<<i<<std::endl: break;
}
Longitudes de campos de bits
const int MAX=5;
struct ejemplo {
const int longitud_bit:MAX;
};
// leer_en_tiempo_de_ejecucion – igual que antes const int MAX=leer_en_tiempo_de_ejecucion();
struct ejemplo {
const int longitud_bit:MAX;
};
Inicializadores de enumeradores

const int MAX=0;
typedef enum HacerVeces {
Ninguno=MAX
UnaVez=1,
Múltiple=2,
};

// leer_en_tiempo_de_ejecucion – igual que antes const int MAX=leer_en_tiempo_de_ejecucion();

typedef enum HacerVeces {
Ninguno=MAX,
UnaVez=1,
Múltiple=2,
};

Argumentos nontype de plantillas integrales o de enumeración

const int MAX=5;

template<int i> class test {};

Cómo crear menús jerárquicos para tu aplicación web: guía completa con descarga gratuita

int main()
{test<MAX>val;}

// leer_en_tiempo_de_ejecucion – igual que antes const int MAX=leer_en_tiempo_de_ejecucion();

template<int i> class test {};

int main()
{test<MAX>val;}

También debes tener en cuenta que algunas optimizaciones no ocurrirán cuando una constante de tiempo de compilación se convierta en una constante de tiempo de ejecución, como se muestra en la Cuadro B.

Las soluciones

Cómo acelerar la carga de imágenes en tu sitio web con pre-carga

Te guiaré a través de la solución final paso a paso. A medida que las implementaciones se vuelven más complicadas, requerirán múltiples archivos y muchas líneas de código. Resaltaré las partes clave del código a medida que avanzo; puedes descargar los archivos completos desde Newsmatic.

Solución 1

Primero, transforma las constantes de tiempo de compilación en constantes de tiempo de ejecución. Comenzarás permitiendo objetos de propiedades. Un objeto de propiedades contiene propiedades; puedes pedir una propiedad específica de un tipo dado. Las propiedades se pueden persistir en un archivo, registro, etc., y la ubicación es transparente para el programador. La Cuadro C y la Cuadro D muestran una implementación que tiene cada propiedad en una línea como esta: <nombre_propiedad> valor_propiedad.

La solución más simple es simplemente inicializar las constantes desde un objeto de propiedades, como esto:
// en lugar de 'const long MAX_USUARIOS = 100;’
const long MAX_USUARIOS = obtener_prop< long>( “max_usuarios”);

Después de examinar la Cuadro E, debes tener en cuenta varios aspectos:

  • app_props.h y app_props.cpp permiten acceder a las propiedades que se leen del archivo props.txt.
  • AllUsers.h y AllUsers.cpp definen una clase sencilla; en su constructor, se obtiene el MAX_USUARIOS, una constante que se inicializa en tiempo de ejecución.
  • main.cpp define un global (s_AllUsers) de tipo AllUsers.
  • props.txt contiene algunas propiedades (max_usuarios y max_fails).

Antes de ejecutar la Cuadro E, ten en cuenta que las variables globales, como s_AllUsers y MAX_USUARIOS, se inicializan antes de main().

Sun anuncia que Java será de código abierto en el futuro próximo

Al ejecutar el ejemplo en la Cuadro E, puedes ver el problema: el constructor de AllUsers accede a la constante MAX_USUARIOS (global). Para el compilador, tanto s_AllUsers como MAX_USUARIOS son variables globales que se inicializan en tiempo de ejecución. No conoces el orden de inicialización de las variables globales, por lo que no puedes saber cuál se inicializa primero. Si s_AllUsers se inicializa antes que MAX_USUARIOS, verá MAX_USUARIOS como 0 (en lugar de 1,000), imprimiendo "hay como máximo 0 usuarios que pueden iniciar sesión". Seguramente no quieres eso.

Ten en cuenta que los problemas solo ocurren cuando las variables globales usan constantes globales (en este caso, s_AllUsers usa MAX_USUARIOS), porque no sabes si estás usando una constante antes de que se haya inicializado. Si una variable global usa una constante local, no hay problema, ya que la constante local se inicializa antes de usarse (Cuadro F). A partir de ahora, cuando me refiera a constantes, siempre me referiré a constantes globales.

Solución 2

La Solución 1 te dio una pista: Distingue entre constantes y globales que dependen de ellas. Las constantes se inicializan como antes y los globales que dependen de ellas se inicializan después. Pero, ¿cómo sabes cuándo comenzar a inicializar los globales que dependen de ellas?

Esto es casi imposible. Pero estás seguro de que cuando estás operando en main(), todas las constantes se han inicializado porque son variables globales. Por lo tanto, puedes inicializar de manera segura las variables globales dependientes.

Para un global dependiente, pospones su construcción almacenando todos los argumentos del constructor en un búfer. Luego, cuando estás en main(), puedes construirlo de manera segura. Solo necesitas cambiar una línea de código:
DbConnection conn( “(local)”, 5); // antiguo
dependent_static< DbConnection> conn( “(local)”, 5); // nuevo

Consejos para desarrolladores de aplicaciones móviles

Por más fácil que suene esta solución, es bastante complicada de traducir en código y llevó más de 2,000 líneas. Aquí hay algunos aspectos destacados:

  • Crea una clase templada (collect_parameters) que almacene todos los argumentos que se le pasan en su construcción.
  • Crea una clase templada (dependent_static< type>) que use los parámetros recopilados para recopilar los argumentos pasados en su construcción. Cuando se llama a initialize(), crea la variable subyacente pasando los argumentos almacenados a su constructor.
  • Llama a all_dependent_statics::initialize_statics() en main() para inicializar todos los estáticos dependientes.

La Cuadro F muestra cómo se ven las clases collect_parameters y dependent_static para hasta dos parámetros. La solución final permite hasta 20 argumentos. Usando estos, solo necesitarás realizar los siguientes cambios en main.cpp y volver a compilar:
#include “dependent_static.h”
dependent_static< AllUsers> s_AllUsers; // solía ser AllUsers s_AllUsers;
all_dependent_statics::initialize_statics(); // colócalo después de int main() {

Antes de pasar a la siguiente solución, date cuenta de que puedes inicializar tus constantes desde múltiples lugares: algunas desde archivos, algunas desde un registro o desde donde quieras. Solo implementa tu propia clase de propiedades.

Solución 3

Las soluciones anteriores obligan a que las constantes se inicialicen antes de main(). No puedes inicializar una constante dentro de main(), ya que no puedes cambiar su valor. Esto es una gran desventaja porque obliga a que el objeto de propiedades que lees se inicialice antes de inicializar la constante:
static properties props;
// antes de que MAX_USERS se inicialice, props debe inicializarse!
const long MAX_USERS = props( “max_users”);

Pero ¿qué sucede si necesitas inicializar todas las constantes desde un archivo cuyo nombre es un argumento de línea de comandos? En este caso, necesitas inicializar las constantes después de ingresar a main(). Usarás una técnica similar a la presentada anteriormente, que incluye estos aspectos:

Cómo manejar múltiples botones de envío en un formulario con PHP
  • Crea una clase con plantilla const_val< type> que represente una constante. (El código del cliente lo usa como si fuera const type.)
  • Pasa el objeto de propiedades y el nombre de la propiedad que deseas usar para inicializarla en su construcción.
  • Inicializa la constante cuando se llama a initialize() en el objeto de propiedades (usando el nombre de la propiedad).
  • Llama a all_constants::initialize_constants(), que llamará a initialize() para todas las constantes const_val existentes en main().

Si intentas usar una constante o un dependent_static antes de que se haya inicializado, lanzará una excepción. Esto es útil en caso de que olvides marcar una variable estática como dependiente. Si una variable estática utiliza una constante de tiempo de ejecución, atraparás este error cuando ejecutes por primera vez la Cuadro G.

Debes tener en cuenta que si el constructor de una variable global lanza una excepción, no podrás atraparla, tu aplicación se terminará (Cuadro H). Sin embargo, esto nunca sucederá en este caso, ya que inicializas las variables globales solo dentro de main(). Incluso puedes rodear la inicialización en un bloque try/catch y finalizar la aplicación de forma más elegante, como se muestra en Cuadro I.

Echa un vistazo a Cuadro J. Ahora tienes un nuevo estático dependiente s_dbConnection. Ten en cuenta que hay dos archivos de propiedades: props.txt y props2.txt. Pasa cada uno de ellos como argumento al programa y observa qué sucede.

Solución 4

Esta solución facilitará el uso de const_val y dependent_static. Al construir un const_val, el nombre de la propiedad pertenece conceptualmente al objeto de propiedades. Es más sencillo decir:
const_val< long> C( obtener_prop( “nombre”) )

que
const_val< long> C( obtener_props(), “nombre” )

Cómo parsear datos XML de manera sencilla con Perl

Esta solución permitirá eso.

Si una constante no se puede inicializar (la propiedad no existe en el objeto de propiedades), se puede dar un valor predeterminado. Solo agrega un argumento adicional:
const_val< long> MAX_USERS( obtener_prop( “max_usuarios”) ); // antiguo
const_val< long> MAX_USERS( obtener_prop( “max_usuarios”), con_valor_predeterminado( 1000) );

La inicialización de una constante puede fallar, en cuyo caso la propiedad no existe; esto lanzará una excepción. Si proporcionaste un valor predeterminado, la constante se establecerá en ese valor y el programa continuará.

Para inicializar tanto constantes como dependent_statics, llama a constants_and_dependents::initialize en lugar de all_constants::initialize_constants() y all_dependent_statics::initialize_statics().

Ahora puedes gestionar las dependencias entre objetos dependent_static. Si dependent_static A depende de dependent_static B, inicializarás A después de B.

Considera el s_AllUsers de la solución anterior, que necesita enviar algunos datos a un s_statisticsLog. Tanto s_AllUsers como s_statisticsLog son dependent_statics. Para que tu aplicación funcione correctamente, necesitas inicializar s_statisticsLog antes de AllUsers. Agregar dependencias es bastante fácil:

Cómo definir y utilizar tipos de datos definidos por el usuario (UDT) en Visual Basic
  • Dale un nombre a B
  • Agrega una dependencia desde A al nombre de B

Aquí tienes un ejemplo:
// Logs.h
dependent_static< statistics_log> s_statisticsLog;
set_dependent_static_name statsname( s_statisticsLog, “statistics”);
// main.cpp
dependent_static< AllUsers> s_AllUsers;
add_dependency_on_static dep( s_AllUsers, “statistics”);

Tanto set_dependent_static_name como add_dependency_on_static son clases de ayuda, cuyo único propósito es darle un nombre a un dependent_static o crear una dependencia.

En Cuadro K, verás dos comentarios FIXME. Si ejecutas el programa tal como está, es probable que obtengas una excepción. Descomenta esas dos líneas, y el programa se ejecutará como se espera.

Solución 5

Intenta lo siguiente:
const_val< char*> NOMBRE_USUARIO( obtener_prop( “nombre_usuario”));

Fallará miserablemente, porque const_val internamente tiene un char * m_val. Al inicializar NOMBRE_USUARIO desde un objeto de propiedades, se copiará una cadena terminada en nulo a m_val. Sin embargo, m_val es NULL y se produce una violación de acceso. Necesitas asignar memoria para m_val, que debe eliminarse al destruir const_val.

Cómo crear un portal empresarial efectivo para tu negocio

Un enfoque es mantener internamente un std::string y proporcionar una conversión const char * al mundo exterior. Esto es justo lo que harás: para las cadenas de char * sin formato, conservarás internamente std::strings. Resolver esto requiere traits, que puedes ampliar para que coincida con tus clases.

En las soluciones anteriores, dependent_statics permitía posponer la inicialización de las variables estáticas. La variable dependent_static se inicializa automáticamente cuando se llama a constants_and_dependents::initialize(). Sin embargo, no es necesario que un dependent_static sea global. El código mostrado en Cuadro L fallará.

En cambio, las constantes const_val solo pueden ser globales. Si deseas que una variable constante sea local a una función, no necesitas const_val. Para una variable local, no quieres posponer su inicialización. De hecho, quieres inicializarla de inmediato usando get_prop_val:
int f() {
  const int MAX_USERS = get_prop_val< int>( obtener_prop( “max_usuarios”));
  // …usa MAX_USERS
}

Eso es todo. Consulta Cuadro M para ver la solución final completa.

Temas avanzados

Cuando transformas una variable global/estática en dependent_static tipo, por lo general, no tienes problemas. Sin embargo, hay algunos casos en los que la transición no es tan sencilla:

  • Cuando quieres pasar una referencia a un objeto
  • Cuando quieres pasar un objeto y su clase no tiene un constructor de copia
  • Cuando quieres pasar const_val u otro dependent_static

La causa de todos estos problemas es el hecho de que dependent_static toma todos los parámetros por valor. Si quieres pasar una referencia, debes usar una clase de referencia, como boost::ref/cref.

Si quieres pasar un objeto cuya clase no tiene un constructor de copia, se producirá un error en tiempo de compilación. Es por eso que debes pasar ese objeto por referencia. Cuando quieres pasar un const_val o otro dependent_static, debes tener en cuenta que no se pueden copiar, por lo que debes pasarlos por referencia. La Cuadro N muestra ejemplos de cada paso de referencia.

Conclusión

Cambiar constantes de tiempo de compilación a tiempo de ejecución y viceversa debería ser sencillo, pero está lejos de serlo. Por eso desarrollé el par const_val y dependent_static, que simplifican este proceso. Aquí tienes una recomendación final: primero, siempre asigna valores predeterminados a las constantes de tiempo de ejecución. Luego, transforma tantas constantes de tiempo de compilación a tiempo de ejecución como sea posible. De esta manera, tendrás múltiples conjuntos de constantes para elegir en tiempo de ejecución, lo que te permitirá probar, depurar y perfilar tu aplicación de manera más fácil.

En Newsmatic nos especializamos en tecnología de vanguardia, contamos con los artículos mas novedosos sobre Desarrollo, allí encontraras muchos artículos similares a Cómo pasar de constantes de tiempo de compilación a constantes en tiempo de ejecución , tenemos lo ultimo en tecnología 2023.

Artículos Relacionados

Subir

Utilizamos cookies para mejorar su experiencia de navegación, mostrarle anuncios o contenidos personalizados y analizar nuestro tráfico. Al hacer clic en “Aceptar todo” usted da su consentimiento a nuestro uso de las cookies.