Menú

Mostrar Mensajes

Esta sección te permite ver todos los mensajes escritos por este usuario. Ten en cuenta que sólo puedes ver los mensajes escritos en zonas a las que tienes acceso en este momento.

Mostrar Mensajes Menú

Mensajes - Loretz

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



#42
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.
#43
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.
#44
...
CitarLe quite el simbolo de referencia en el parámetro para la funcion de la linea 5 y ahí compiló..
Pero no veo por qué...

Para poner las cosas más a mano, quitando de tu ejemplo lo que no viene al caso:
Código (cpp) [Seleccionar]
#include <iostream>
#include <thread>
#include <vector>

void f(std::vector<double>& vd) {
   for (auto& v : vd)
       std::cout << "From f -> " << v << std::endl;
}

int main() {
   std::vector<double> vd{ 2.5, 5.5, 7.1 };

   std::thread t0{ f, vd };      //<-- Error : No type named type in clas std::result_of))(std::vector&)> Si comento esta linea compila...

   t0.join();

   return 0;
}


Y dices que si quitas la referencia, si la función "f" toma su argumento por valor, entonces compila sin error. Está bien, y quisieras saber por qué...

Porque el constructor de thread que estás invocando es:
template< class Function, class... Args >
explicit thread( Function&& f, Args&&... args );

Ver: https://en.cppreference.com/w/cpp/thread/thread/thread

Y en ese caso, en las NOTAS, se aclara que
Citar"The arguments to the thread function are moved or copied by value. If a reference argument needs to be passed to the thread function, it has to be wrapped (e.g. with std::ref or std::cref). "

NO es que lo esté aclarando, sólo se dice que "Así debe ser",

Entonces, en tu ejemplo, podrías poner:

Código (cpp) [Seleccionar]
std::thread t0{ f, std::ref(vd) };

Funciona así porque std::ref devuelve ese wrapper que necesitas en el segundo argumento template, que se pasa por valor, pero se comporta como una referencia (encapsula, contiene, envuelve una referencia). https://en.cppreference.com/w/cpp/utility/functional/ref


Pero preguntabas "por qué".

Bueno, porque al construir la instancia de thread, en su constructor se invoca a la función del primer parámentro, con los parámetros del segundo parámetro, ¿de acuerdo?, pero esa llamada a f se hace a través de
Código (cpp) [Seleccionar]
std::invoke(decay_copy(std::forward<Function>(f)),
           decay_copy(std::forward<Args>(args))...);


Donde se invoca la función f con el "parameter pack" que devuelva "decay_copy", ¿verdad?

Pero esa "decay_copy" sólo hace un "forward" del pack que recibe, y devuelve a su vez un std::decay_t.

Y, ajá, acá está, es eso. Si te fijas en la documentación sobre std::dacay_t: https://en.cppreference.com/w/cpp/types/decay verás que para este caso:

Citarthe member typedef type is std::remove_cv<std::remove_reference<T>::type>::type.
These conversions model the type conversion applied to all function arguments when passed by value.

Ahorá sí, no sólo está aplicando std::remove_cv, que no tiene que ver con lo nuestro en este momento, sino que, fundamentalmente, aplica std::remove_reference al T::type que recibe como parámetro, que eso sí termina de explicar por qué necesitamos un wrapper cuando la función que se va a ejecutar en el nuevo thread toma sus parámetros por referencia.






Quise editar mi respuesta anterior y quedó una completa basura, así que escribo aquí a modo de apéndice:

Otra variante para considerar es que el parámetro de f sea una const reference:
void f(const std::vector<double>& vd)

o, como hay quien prefiere:
void f(std::vector<double> const& vd)
(con el "const" antes de &)
#45
Como siempre, una simple pregunta puede tener una respuesta simple, o no.

Como regla de estilo general, las clases de la biblioteca estándar C++ (como casi cualquier otra cosa en el mundo) pueden usarse y/o entenderse; con una fuerte inclinación a "usarse" y como caso excepcional la parte de "entenderse". Es la forma que se adoptó para todo lo que llamamos conocimiento; no sé cuándo sucedió, pero cuando yo llegué ya estaba así.

Y en particular, hablando de
CitarQue vendría siendo ifstream ?.. un canal que conecta a un archivo, o el archivo mismo?
o de
Citary que vendría siendo el istream_iterator ?
tienes también las mismas opciones: un par de respuestas dogmáticas, del tipo "es así y deja de joder", o meterte en entender de qué se trata (que supongo que es un poco menos deshumanizante, aunque tampoco demasiado acojonante).

Bueno, ¿y qué tal intentar algo equilibrado? (Que no es lo mío, aclaro). Puedo intentar algo...

¿Que qué es un ifstream?
Una especialización para chars de la clase templatre basic_ifstram.
https://en.cppreference.com/w/cpp/io/basic_ifstream

¿No está claro?
Es una clase para hacer simple lo que de otro modo puede resultar más complejo. Es la idea de ser "de alto nivel" de los lenguajes.
std::ifstream es una clase que presenta de una manera simplificada (de alto nivel) la forma de acceder a un archivo como "flujo de entrada". Así, se puede leer en un archivo como si se estuviera usando "cin" (el flujo de caracteres de la entrada estándar).

    std::ifstream entrada("archivo_de_entrada.txt");
    int i;
    double d;
    entrada >> i >> d;


¿Es así de simple?
Casi, también se puede sobrecargar el operador >> para tipos definidos por uno (el programador usuario de la clase), y hacer que la lectura de nuestras propias estructuras de datos sea así de fácil.

¿Sin trampas?
Por supuesto que hay trampas, muchísimas, como todo lo complejo que es fácil de usar, yo no puedo construir mi propia bicicleta pero puedo usarla, más o menos.

¿Qué es un istream_iterator?
Es un iterador de entrada capaz de acceder sucesivamente a los elementos en un "flujo de entrada" (un "input stream", como ifstream o cin).
https://en.cppreference.com/w/cpp/iterator/istream_iterator

¿En dónde se usan?
Comúnmente en algoritmos de la biblioteca estándar que acceder a conjuntos de información por medio de "input iterators" como parámetros.

¿Se pueden usar en código más artesanal para acceder a los elementos de un ifstream, por ejemplo?
Sí, por supuesto, pero pierde toda la gracia.


#46

int x = 3;
int y {4};
int z(5);

Y podrías incluir también a la cuarta:
int t = {6}; // copy initialization

Citar... el problema es cuando usas elementos como un contenedor.. ya que los contenedores aceptan una lista de argumentos encerradas en llave, sin embargo esto me confunde un poco, porque no se cuando está inicializando o cuando está pasando por argumento a un constructor.. o cuando está usando un constructor de conversión.. etc..

Hablando de objetos, "construir" implica "inicializar"; cuando se construye un objeto se lo está inicializando de alguna manera:

  • zero initialization
  • default initialization
  • direct initialization
  • value initialization
  • copy initialization
  • list initialization
Pudes empezar por https://en.cppreference.com/w/cpp/language/copy_initialization

Y con respecto a forma de construir un std::vector o cualquier contenedor de la biblioteca estándar, creo que lo mejor es tener siempre a mano su documentación, porque algunas veces lo que parece intuitivo no lo es tanto; y en particular, los constructores a partir de una std::initializer_list han tenido algunos cambios en C++17.

Citarvector<string> vs { "Hola", " Que tal"};     //No hay problema..  (1)
vector<string> vs2 { 5, 1 };                     //Houston (2)

istream_iterator<string> is;
istream_iterator<string> is2;

vector<string> vs3{is, is2};                     //No hay problema.. por que? (3)

Puedes ver los constructores de vector en https://en.cppreference.com/w/cpp/container/vector/vector
Por ejemplo, para
1) Construye (inicializando) vs con una "initializer list" de dos strings
2) Error: no hay un constructor para vector<string> que tome una initializer list de ints.
3) Invoca el constructor:
template< class InputIt >
vector( InputIt first, InputIt last,
        const Allocator& alloc = Allocator() );

aunque cuidado, en este caso estás usando un rango de dos iteradores nulos ("end of stram iterators") [https://en.cppreference.com/w/cpp/iterator/istream_iterator/istream_iterator]

Yo me quedaría con la idea de que cuando se va a utilizar alguna función miembro de una clase (constructor o lo que sea), siempre conviene tener la documentación a mano.





#47
Programación C/C++ / Re: Error C2662 - const
8 Julio 2019, 02:38 AM
En general necesitas las dos versiones para begin y para end, las const y no const, para que pueda usarse la que corresponda en cada caso.

Por ejemplo, si tienes: definidas las cuatro funciones miembro:
Código (cpp) [Seleccionar]
template <typename T>
    T* s<T>::begin() {
        return elem;
    }

    template <typename T>
    const T* s<T>::begin() const {
        return elem;
    }

    template <typename T>
    T* s<T>::end() {
        return begin() + size;
    }

    template <typename T>
    const T* s<T>::end() const {
        return begin() + size;
    }


Podrás usar tanto
Código (cpp) [Seleccionar]
template <typename C, typename V>
V sum(C& c, V v) {  // c es una referencia no constante
    for (auto a : c) { // usa las versiones no const de begin() y de end()
        v += a;
    }
    return v;
}


como
Código (cpp) [Seleccionar]
template <typename C, typename V>
V sum(const C& c, V v) {  // c es una referencia constante
    for (auto a : c) {          // usa las versiones const de begin() y de end()
        v += a;
    }
    return v;
}
#48
Un momento...

El orden en que se inicializarán los miembros de la struct x es en el orden en que se encuentren declarados en la struct; en este caso, primero iArray y después tam, y no importa en qué orden se escriban en la 'initializer list' del constructor.

Hay un problema aquí donde dices:
x x1 = x0;   //copia correcta...

Al invocar el copy constructor se va a ejecutar la inicialización de iArray antes que tam, de modo que la expresión
iArray{new int[tam]}
al pretender acceder a tam sin inicializar se incurrirá en "Undefined behavior" (o sea, cualquier cosa --normalmente mala-- puede suceder).

#49
Una parte de una string es una substring.

En tu ejemplo sería:

std::cout << a.substr(2, 3);

https://en.cppreference.com/w/cpp/string/basic_string/substr


#50
void extraerarchivo(stack <persona> X)

Estás pasando una copia de stack<persona>  X, que al salir de la función se destruirá. Si necesitas modificar una stack<persona> ya creada puedes pasar una referencia (o un puntero) o, mejor, la función extrerarchivo puede devolver una stack<persona>. y usar así:

stack<persona> extraerarchivo();

...


stack <persona> X = extraerarchivo();


supongo que hasta sería mejor si a la función eztraerarchivo le pasas también el iftream de dónde leer los datos, pero eso ya es otra cirugía.