C++ Clases de almacenamiento
Las clases de almacenamiento definen el ámbito (visibilidad) y el ciclo de vida de las variables/funciones en un programa de C++. Estos especificadores se colocan antes del tipo que modifican. A continuación se enumeran las clases de almacenamiento disponibles en un programa de C++:
auto: Este es el especificador de clase de almacenamiento predeterminado y normalmente se puede omitir. Las variables especificadas con auto tienen un período de almacenamiento automático, es decir, su vida útil se limita al bloque (block) donde se definen. Las variables auto suelen asignarse en la pila.
register: Se utiliza para sugerir al compilador que almacene la variable en un registro de la CPU para aumentar la velocidad de acceso. En C++11 y versiones posteriores, register es una característica obsoleta y ya no tiene un efecto real.
static: Se utiliza para definir variables o funciones con período de almacenamiento estático; su vida útil abarca todo el tiempo de ejecución del programa. Dentro de una función, el valor de una variable static permanece constante entre llamadas a la función. Dentro de un archivo o en el ámbito global, las variables static tienen enlace interno y solo se pueden acceder en el archivo donde se definen.
extern: Se utiliza para declarar variables o funciones con enlace externo, que se pueden compartir entre múltiples archivos. De forma predeterminada, las variables y funciones globales tienen clase de almacenamiento extern. Usar extern en un archivo para declarar una variable o función global definida en otro archivo permite compartirla entre archivos.
mutable (C++11): Se utiliza para modificar variables miembro de una clase, permitiendo modificar el valor de estas variables en funciones miembro const. Normalmente se usa para datos como cachés o contadores que necesitan ser modificados en un contexto const.
thread_local (C++11): Se utiliza para definir variables con período de almacenamiento local de hilo; cada hilo tiene su propia copia independiente. La vida útil de una variable local de hilo es la misma que la del hilo.
Desde C++17, la palabra clave auto ya no es un especificador de clase de almacenamiento de C++, y la palabra clave register está en desuso.
Los especificadores de clase de almacenamiento proporcionan a los programadores los medios para controlar la vida útil y la visibilidad de variables y funciones.
El uso adecuado de los especificadores de clase de almacenamiento puede mejorar la mantenibilidad y el rendimiento del programa.
Desde C++11, register ha perdido su función original, mientras que mutable y thread_local son características recién introducidas para resolver problemas de programación específicos.
A continuación se muestra un ejemplo que ilustra diferentes especificadores de clase de almacenamiento:
Ejemplos
// Variable global, con enlace externo, clase de almacenamiento predeterminada extern
int globalVar;
void function() {
// Variable local, con período de almacenamiento automático, clase de almacenamiento predeterminada auto
auto int localVar = 10;
// Variable estática, con período de almacenamiento estático, su vida útil abarca todo el programa
static int staticVar = 20;
const int constVar = 30; // Las variables const tienen por defecto un período de almacenamiento static
// Intentar modificar una variable const, error de compilación
// constVar = 40;
// Variable miembro mutable, se puede modificar en funciones miembro const
class MyClass {
public:
mutable int mutableVar;
void constMemberFunc() const {
mutableVar = 50; // Permite modificar la variable miembro mutable
}
};
// Variable local de hilo, cada hilo tiene su propia copia independiente
thread_local int threadVar = 60;
}
int main() {
extern int externalVar; // Declarar una variable con enlace externo
function();
return 0;
}
Clase de almacenamiento auto
Desde C++11,autola palabra clave se utiliza en dos casos: al declarar una variable, se deduce automáticamente el tipo de la variable según la expresión de inicialización; al declarar una función, es un marcador de posición para el tipo de retorno de la función.
En el estándar C++98, la palabra clave auto se usaba para declarar variables automáticas, pero como su uso era escaso y redundante, se eliminó en C++17.
Deduce automáticamente el tipo de la variable declarada según la expresión de inicialización, por ejemplo:
Clase de almacenamiento register
register es una clase de almacenamiento (storage class) que se utiliza para declarar variables e indicar al compilador que las almacene en registros para un acceso rápido.
Usar la palabra clave register puede aumentar la velocidad de ejecución del programa, ya que reduce el número de accesos a la memoria.
Sin embargo, hay que tener en cuenta que la clase de almacenamiento register es solo una sugerencia; el compilador puede ignorarla, porque los compiladores modernos suelen optimizar el código automáticamente y elegir la ubicación de almacenamiento adecuada.
Formato de sintaxis:
register data_type variable_name;
registerEs la palabra clave de la clase de almacenamiento, utilizada para indicar al compilador que almacene la variable en un registro.data_typeEs el tipo de dato de la variable, puede ser cualquier tipo de dato válido de C++.variable_nameEs el nombre de la variable.
La clase de almacenamiento register se utiliza para indicar al compilador que almacene la variable en un registro para mejorar la velocidad de acceso. Sin embargo, debido a la capacidad de optimización automática de los compiladores modernos, no es necesario usar la palabra clave register y rara vez se utiliza en la práctica.
En el estándar C++11, la palabra clave register ya no es un especificador de clase de almacenamiento, sino una característica obsoleta. Esto significa que en C++11 y versiones posteriores, el uso de la palabra clave register no tendrá ningún efecto en el programa.
En C++, se pueden usar referencias o punteros para mejorar la velocidad de acceso, especialmente al trabajar con estructuras de datos grandes.
Clase de almacenamiento static
staticLa clase de almacenamiento indica al compilador que mantenga la existencia de una variable local durante todo el ciclo de vida del programa, sin necesidad de crearla y destruirla cada vez que entra y sale del ámbito. Por lo tanto, usar static para modificar una variable local permite mantener su valor entre llamadas a funciones.
El modificador static también se puede aplicar a variables globales. Cuando static modifica una variable global, restringe el ámbito de la variable al archivo donde se declara.
En C++, cuando static se usa en un miembro de datos de una clase, hace que solo exista una copia de ese miembro compartida por todos los objetos de la clase.
Ejemplos
Cuando el código anterior se compila y ejecuta, produce los siguientes resultados:
变量 i 为 6 , 变量 count 为 9 变量 i 为 7 , 变量 count 为 8 变量 i 为 8 , 变量 count 为 7 变量 i 为 9 , 变量 count 为 6 变量 i 为 10 , 变量 count 为 5 变量 i 为 11 , 变量 count 为 4 变量 i 为 12 , 变量 count 为 3 变量 i 为 13 , 变量 count 为 2 变量 i 为 14 , 变量 count 为 1 变量 i 为 15 , 变量 count 为 0
Clase de almacenamiento extern
externLa clase de almacenamiento se utiliza para proporcionar una referencia a una variable global, que es visible para todos los archivos del programa. Cuando se usa 'extern', para variables que no se pueden inicializar, el nombre de la variable apunta a una ubicación de almacenamiento definida previamente.
Cuando tenga varios archivos y defina una variable o función global que se pueda usar en otros archivos, puede usar en otros archivosexternpara obtener una referencia a la variable o función ya definida. Se puede entender así:externse utiliza para declarar una variable o función global en otro archivo.
El modificador extern se usa normalmente cuando dos o más archivos comparten las mismas variables o funciones globales, como se muestra a continuación:
Primer archivo: main.cpp
Ejemplos
Segundo archivo: support.cpp
Ejemplos
Aquí, en el segundo archivo, laexternpalabra clave se utiliza para declarar count, que ya está definido en el primer archivo main.cpp. Ahora, compile estos dos archivos, de la siguiente manera:
$ g++ main.cpp support.cpp -o write
Esto produciráwriteun programa ejecutable, intente ejecutarwrite, producirá los siguientes resultados:
$ ./write Count is 5
Clase de almacenamiento mutable
mutable es una palabra clave que se utiliza para modificar variables miembro de una clase, permitiendo que se modifiquen en funciones miembro const. Normalmente, una función miembro const no puede modificar el estado del objeto, pero si una variable miembro se declara como mutable, se puede modificar en una función const.
Características:
- Permite modificar:
mutableLa variable miembro puede serconstmodificada dentro de la función miembro. - Propósito de diseño: normalmente se utiliza en escenarios donde se necesita gestionar el estado sin cambiar el estado externo del objeto, como cachés, cálculo diferido, etc.
Ejemplos
class Example {
public:
Example() : value(0), cachedValue(0) {}
// Función miembro constante
int getValue() const {
return value; // Leer miembro constante
}
// Modificar miembro mutable
void increment() {
++value;
cachedValue = value * 2; // Modificar miembro mutable
}
int getCachedValue() const {
return cachedValue; // Leer miembro mutable
}
private:
int value; // Miembro normal, no se puede modificar en una función const
mutable int cachedValue; // Miembro modificable, se puede modificar en una función const
};
int main() {
const Example ex;
// ex.increment(); // Error: no se puede llamar a una función no const en un objeto const
// ex.value = 10; // Error: no se puede modificar un miembro de un objeto const
std::cout << "Value: " << ex.getValue() << std::endl;
std::cout << "Cached Value: " << ex.getCachedValue() << std::endl; // La salida es 0
return 0;
}
Escenarios aplicables:
- Caché: En
constCalcular y almacenar en caché el resultado en la función, sin afectar el estado externo del objeto. - Seguimiento de estado: como contadores de registro, rastrear información como el número de llamadas, evitando modificaciones intrusivas en la lógica de la clase.
Notas:
mutableEl uso de variables debe ser cuidadoso para evitar cambios de estado inesperados que afecten la legibilidad y mantenibilidad del código.mutableAdecuado para situaciones específicas donde se necesitaconstcambiar el estado en el entorno, no como un patrón de diseño general.
Clase de almacenamiento thread_local
thread_local es una clase de almacenamiento introducida en C++11, utilizada para gestionar variables específicas de un hilo en entornos multiproceso.
Las variables modificadas con thread_local tienen una instancia independiente en cada hilo, por lo que las operaciones de cada hilo sobre dicha variable no afectan a otros hilos.
- Independencia: cada hilo tiene su propia copia independiente de la variable, y las operaciones de lectura/escritura entre diferentes hilos no interfieren entre sí.
- Ciclo de vida:
thread_localLa variable se destruye automáticamente al finalizar su hilo. - Inicialización:
thread_localLa variable puede tener inicialización estática o dinámica, y admite inicialización en el momento de la declaración.
thread_local es adecuado para escenarios que requieren almacenar estado del hilo, cachés o evitar condiciones de carrera, como grupos de hilos, contextos de solicitud, etc.
Lo siguiente demuestra variables que pueden declararse como thread_local:
Notas:
- Rendimiento: debido a que cada hilo tiene una copia independiente,
thread_localel acceso a la variable puede ser ligeramente más lento que a variables globales o estáticas. - Almacenamiento estático:
thread_localEl tipo de almacenamiento de la variable tiene duración de almacenamiento estático, por lo que existe durante toda la ejecución del programa.