Variables estaticas...

Iniciado por digimikeh, 29 Agosto 2019, 05:16 AM

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

digimikeh

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
Código (cpp) [Seleccionar]

#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
Código (cpp) [Seleccionar]

#pragma once
struct Archivo1{
void printVal() const;
};


Archivo1.cpp
Código (cpp) [Seleccionar]

#include "Archivo2.h"
#include "Archivo1.h"
#include <iostream>

void Archivo1::printVal() const {
std::cout << "Desde Archivo1 -> " << x::num << "Memoria : " << &x::num;

}


Archivo2.h
Código (cpp) [Seleccionar]

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_!

Dungeons & dragons;
dragons.Attack();

@XSStringManolo

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.

Loretz

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.

digimikeh

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

Saludos..
Dungeons & dragons;
dragons.Attack();

Loretz

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)

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.

digimikeh

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....


Dungeons & dragons;
dragons.Attack();

Loretz

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.




digimikeh

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 ...

Código (cpp) [Seleccionar]

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 ? :

Código (cpp) [Seleccionar]

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.
Dungeons & dragons;
dragons.Attack();

Loretz

#8
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.



digimikeh

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
Código (cpp) [Seleccionar]

class Alpha{
     int x;
public:
     Alpha() : x{5}{}
     //hacer otras cosas
}


main.cpp
Código (cpp) [Seleccionar]

#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...


Dungeons & dragons;
dragons.Attack();