Muy buenas..
Tengo un mal concepto quizá de lo que son las variables estáticas, yo las imagino algo asi como un contenedor que se crea automáticamente al iniciarse un programa, vive durante todo el programa y luego se cierra una vez que el programa termina... como un bolso de viaje que tomas al iniciar el viaje y te deshaces de él cuando regresas a casa... asi es como yo las veo.. (por favor, indicarme si estoy mal).
El siguiente código no esta reflejando esto:
main.cpp
#include <iostream>
#include "Archivo2.h"
#include "Archivo1.h"
int main() {
x::num = 10;
std::cout << "Desde main -> " << x::num << "Memoria : " << &x::num;
std::cout << std::endl;
Archivo1 a;
a.printVal();
return 0;
}
Archivo1.h
#pragma once
struct Archivo1{
void printVal() const;
};
Archivo1.cpp
#include "Archivo2.h"
#include "Archivo1.h"
#include <iostream>
void Archivo1::printVal() const {
std::cout << "Desde Archivo1 -> " << x::num << "Memoria : " << &x::num;
}
Archivo2.h
namespace x{
static int num;
}
La salida de consola "Desde Main ->" imprime 10
La salida de consola "Desde Archivo1 ->" imprime 0...
Creí que estaba imprimiendo el mismo elemento, pero no, cuando le agregue que imprimiera la dirección de memoria, me encontré con la sorpresa de que ambas estan imprimiendo direcciones diferentes....
En qué momento se creó una nueva instancia de la variable estática y como puedo solucionar este dilema?.. mi intención es escribir e imprimir en una sola variable x::num
Saludos_!
Prueba añadiendo external antes de static int num; en el archivo2.h
Parece un problema de linkado, que te crea otra static nueva en el .cpp.
El contenido de los archivos header (*.h) se copia y pega en cada lugar donde el compilador encuentra su #include. En tu caso se está escribiendo
namespace x {
inline static int num;
}
en cada lugar donde se encuentre
#include "Archivo2.h"
Así, tendrás una x::num en cada cpp, una inicializada a 10 y la otra a 0 ("zero initialized", como exige el estándar).
Pero, además, las variables static tiene un alcance ("scope") a nivel de unidad de traducción (básicamente cada cosa que vaya a compilarse como *.obj), así que es natural que tengas una nueva x::num en cada cpp, esa es su naturaleza. Si quieres que sea la misma, no debería ser static.
Gracias por las respuestas...
Manolo, te refieres a extern ?
Loretz, entonces las variables estáticas se instancian mediante la inclusión de archivos y no de tipos ... es asi?... por ejemplo, lo que has dicho: estoy incluyendo el mismo archivo en dos archivos independientes, serian dos instancias separadas de la variable estatica... sin embargo, si instancio dos tipos Archivo2 en un mismo archivo, ahi si estaria utilizando la misma variable estatica para ambos casos.... ?
Suponiendo que tengo una clase llamada Alpha, que tiene un campo publico estático llamado myName, si yo instancio esta clase mas de una vez dentro del mismo archivo fuente, todas las instancias de la clase estarían accediendo a la misma dirección de memoria de myName...
Es al menos lo que veo aqui en este ejemplo:
https://www.tutorialspoint.com/cplusplus/cpp_static_members.htm (https://www.tutorialspoint.com/cplusplus/cpp_static_members.htm)
Saludos..
CitarLoretz, entonces las variables estáticas se instancian mediante la inclusión de archivos y no de tipos ... es asi?...
No no. Lo que quise decir es que cualquier cosa que escribas en un archivo.h se va a "pegar" en el archivo.cpp justo ahí donde hayas puesto "#include archivo.h". Es como si el compilador hiciera un "buscar y reemplazar", y ahí donde encuentre "#include archivo.h" lo va a reemplazar por el contenido de ese archivo. Sea lo que sea que haya escrito en ese .h.
Citar... sin embargo, si instancio dos tipos Archivo2 en un mismo archivo, ahi si estaria utilizando la misma variable estatica para ambos casos.... ?
Esta parte no la entiendo. Si a lo que te refieres es a si escribes dos veces el mismo #include en un mismo *.cpp, si has puesto la guarda "pragma once" (o las directivas #ifndef ... #define ... #endif) el compilador ignorará la segunda inclusión; y si no la has puesto, tendrás un error al compilar, porque estarías violando la regla "ODR" (one definition rule". (Puedes consultar la definición en https://en.cppreference.com/w/cpp/language/definition (https://en.cppreference.com/w/cpp/language/definition))
CitarSuponiendo que tengo una clase llamada Alpha, que tiene un campo publico estático llamado myName ...
Aquí sí, es como dices y se muestra en el ejemplo que mencionas. Pero en este caso ya no sería una variable static sino un dato miembro declarado static, que es otra cosa. Siguen reglas distintas.
La cita 2 está relacionada con la 3... es que no me supe explicar correctamente...lo siento :rolleyes:
Entonces la solución que tengo es lo que planteaba Manolo std::string con la palabra extern de lo contrario sería usar objetos persistentes pero no creo que esto sea muy elegante....
Si lo que necesitas es una misma variable accesible desde distintos cpp, deberías declararla "extern", pero ya no sería posible que fuera "static", porque en este sentido son contradictorias.
Por ejemplo:
en Archivo1.h
#pragma once
struct Archivo1 {
void printVal() const;
};
en Archivo2.h
#pragma once
namespace x {
extern int num; // no puede ser static
}
en Archivo1.cpp
#include "Archivo1.h"
#include "Archivo2.h"
#include <iostream>
void Archivo1::printVal() const {
std::cout << "Desde Archivo1 -> " << x::num << " Memoria : " << &x::num << '\n';
}
en main.cpp
#include <iostream>
#include "Archivo2.h"
#include "Archivo1.h"
int x::num = 10; // definida aquí o en cualquier otro cpp
int main() {
std::cout << "Desde main -> " << x::num << " Memoria : " << &x::num << '\n';
Archivo1 a;
a.printVal();
return 0;
}
Salida:
Desde main -> 10 Memoria : 00FBC000 // mismo valor, misma dirección de memoria.
Desde Archivo1 -> 10 Memoria : 00FBC000
Al declararla como "extern" le dices al compilador que está definida en algún otro lado, y es conocida en todo el programa, en cualquier cpp donde se incluya su declaración (por medio de #include Archivo2.h).
Esto mismo también puede hacerse (a partir de C++17) con
Archivo2.h
#pragma once
namespace x {
inline int num = 10;
}
Y ya no necesitas definir x::num en ninguno de los cpp.
De acuerdo, me parece lógico...
Bueno, en este caso he querido usar un int como ejemplo para describir mi duda, en el programa que estoy intentando hacer, lo que quiero usar como variable global es un tipo propio que se inicializa con un constructor. En el constructor de ese tipo propio (que se llama Sesion) se inicializan los campos privados, por lo que ...
extern Sesion s;
...estaría inicializando dichos campos automáticamente, aún asi debo volver a definir tal como lo hiciste cuando escribiste esta linea ? :
int x::num = 10; // definida aquí o en cualquier otro cpp
O es que al escribir extern Sesion s en realidad no estoy ejecutando el constructor con su inicialización?
Saludos! y gracias por las respuestas... han sido de mucha ayuda @std::string Manolo y @Loretz.
Para objetos de alguna clase es lo mismo que para tipos nativos, puedes usar las dos formas, declarar el objeto como extern o como inline (mejor).
Por ejemplo, usando
extern podría ser algo así:
Archivo1.h#pragma once
#include <string>
extern struct X {
X(std::string str) : str(str) {}
int num = 10;
std::string str;
} x;
Y en algún cpp
X x{"hola"};
Usando inline no necesitas que la definición esté en un cpp
#pragma once
#include <string>
inline struct X {
X(std::string str) : str(str) {}
int num = 10;
std::string str;
} x{"hola"};
Estos son un par de ejemplos de cómo podía hacerse antes y ahora con el estándar C++17, pero hay otras cosas a considerar, probablemente necesites un constructor por defecto, y asignarle valores a tu objeto cuando el programa los conozca, o crear el objeto en la memoria libre (usando un smart pointer, por supuesto), o ... Bueno, ya verás.
[EDITO] Ah, me olvidaba
CitarO es que al escribir extern Sesion s en realidad no estoy ejecutando el constructor con su inicialización?
No, no estás ejecutando el constructor, porque
extern Sesion es una declaración no una definición.
ok, entiendo, pero yo tenía entendido que cuando tu definías algo, estas usando el constructor... o es que extern es la excepción?
Alpha.h
class Alpha{
int x;
public:
Alpha() : x{5}{}
//hacer otras cosas
}
main.cpp
#include "Alpha.h"
int main (){
Alpha a; //Nota (a)
}
Nota (a) : A pesar de estar declarando un objeto llamado a de tipo Alpha se estaría inicializando el campo privado x y valdría 5... (es asi como yo tengo en mi programa el objeto Sesion inicializa sus campos privados a un string "NA".
Sin embargo, acabo de darme cuenta que incializar y definir no son la misma cosa...
Citarok, entiendo, pero yo tenía entendido que cuando tu definías algo, estas usando el constructor... o es que extern es la excepción?
Exactamente, así es;
extern es para indicar que se trata de una declaración, no una definición, por eso es que la definición debe ir en algún otro lado.
De todos modos hoy (a partir del C++17) es preferible la declaración
inline https://en.cppreference.com/w/cpp/language/inline
CitarSin embargo, acabo de darme cuenta que incializar y definir no son la misma cosa...
... Más o menos... o, mejor, casi nunca...
Declarar, definir, inicializar, son palabras que tienen significados muy específicos, y con unos cuantos casos particulares, que algunas veces no ayudan para nada a la intuición.
En algunos casos la declaración y la definición son la misma cosa;
En casi todos los casos una definición implica la inicialización.
En matemática elemental tienes palabras como minuendo, sustraendo, resta, diferencia, que no pueden confundirse; o, también, dividendo, divisor, cociente, división. 0, ¿qué tal porciento, porcentaje, tasa, razón? Cada una significa algo específico pero que en el habla común usamos sin mucho cuidado. Bueno, en C++ es mucho peor.
Muy bien ya me ha quedando claro...
:o
Gracias..