martes, 5 de noviembre de 2013

Como trabajar con CMake en Eclipse?

Hacia tiempo que queria cambiar otra vez a eclipse, por que aunque netbeans me gusta, lo encuentro limitado. La ventaja que tiene netbeans es su soporte nativo para CMake. En cambio, con eclipse tenia que limitarme a escribir mi archivo CMakeLists.txt y crear el makefile para despues incluirlo en eclipse. Hace poco encontré una manera alternativa de usar CMake desde dentro de eclipse que completaba la cadena. Vamos a ver como se hace

1.- Crear un nuevo proyecto

Creamos un nuevo proyecto C/C++ en eclipse desde 0. Es decir, no usamos la opción de crear un "Makefile Project with Existing Code".
Le vamos a añadir codigo fuente de ejemplo:
main.cpp
#include "main.h"
int main() {
); return EX
greetings (IT_SUCCESS;
}
main.h
#include <iostream>
void greetings() {
a" << std::endl; }
std::cout << "Ho
l
Ademas del código vamos a añadir un CMakeLists.txt directamente en eclipse.
cmake_minimum_required(VERSION 2.8)
project(cmake_test) SET( TEST_SRC
rc/main.h ) add_executable(te
src/CMakeInEclipseTest.cpp
sst_common ${TEST_SRC})



2.- Creando los targets de eclipse

Vamos a aprovechar que podemos ejecutar instrucciones de CMake desde linea de comandos con la opcion -E para usarlo con los targets de eclipse. Primero vamos a ver la ventana de Make Target en Window | Show View | Make Target . Debe aparecer a la derecha.


Ahora seleccionamos la carpeta que sera la carpeta de trabajo de CMake. Hacemos click derecho y seleccionamos Create Make Target.
Vamos a ponerle un nombre a este target CMake Release
En la ventana Make Target, quitamos la selección "Same as the target name", y nos aseguramos que el campo este vacio.


En build command, deseleccionamos Use Builder settings y escribimos en build command algo como
cmake -E chdir ${ProjDirPath}/build/Release/ cmake -G "Unix Makefiles" ../../ -DCMAKE_BUILD_TYPE:STRING=Release

En este momento quedaria asi.
Lo que intento es que el código intermedio y caches generadas por cmake quede en una carpeta "build" para que no ensucie la carpeta de proyecto. Dentro de buid estará "Release" y "Debug" donde se generan las versiones correspondientes. 

Para probarlo, nos aseguramos de que  ${ProjDirPath}/build/Release existe y si hacemos doble click deberiamos ver en la consola de Eclipse la ejecución de CMake.

Podemos crear más targets que perfilen la construcción del proyecto (Debug, con diferentes flags, ...)





3.- Configurar el Eclipse CDT Builder

Hacemos click derecho en el proyecto y vamos a las preferencias. Vamos a C/C++ Build y y alli seleccionamos Configuration: [All configurations]. Seleccionamos la pestaña Builder Settings  y quitamos el check de Use default build command. En build command ponemos make -C build/${ConfigName} . Desactivamos Generate Makefiles automatically y dejamos en blanco Build directory. En resumen quedaria algo asi: 

La pestaña de bahaviour la dejamos igual. Le damos a apply y a OK y ahora nos vamos project -> build project  y vemos como el proyecto se construye. Con esto deberiamos tener toda la cadena CMake + Make + codigo en funcionamiento en nuestro Eclipse.

miércoles, 7 de marzo de 2012

Run-Time Type Information

Pues sigo con mi motor y lo que he hecho ahora es darle la capacidad a las clases de obtener información sobre su tipo. Esta técnica se puede activar por compilador pero es poco portable y muy ineficiente (por que se activa para todas las clases y con soporte para herencia múltiples). En mi caso lo hago restringiendo a herencia simple y solo para las clases que hereden de object. El UML es este:
Uso object como concentrador de utilidades que le voy dando a todo el resto de clases del motor, asi que rtti no se usa directamente sino a traves de objet. El codigo es este:
class Rtti
{
public:

    Rtti (const char* name, const Rtti* baseType);
    ~Rtti ();

    inline const char* GetName () const;
    inline bool IsExactly (const Rtti& type) const;
    bool IsDerived (const Rtti& type) const;

private:
    const char* m_name;
    const Rtti* m_baseType;
};
Como ves el nombre del tipo va en name y el tipo de la base es un puntero a una clase de tipo Rtti. Cuando queremos saber el tipo de una clase solo tenemos que hacer:
nombreClase::Rtti::GetName();
Y podemos saber si una clase es de un tipo asi:
if( objeto1::Rtti.IsExactly( objeto2::Rtti ) )
o tambien asi
if( objeto1.GetRttiType().IsExactly( objeto2.GetRttiType() )
Es en el objeto zelObject donde pongo el objeto Rtti para que lo hereden el resto de clases:
class zelObject
{
// Run-time type information.
public:
    virtual const Rtti& GetRttiType () const;
    bool IsExactly (const Rtti& type) const;
    bool IsDerived (const Rtti& type) const;
    bool IsExactlyTypeOf (const zelObject* object) const;
    bool IsDerivedTypeOf (const zelObject* object) const;
    static const Rtti TYPE;


// Abstract base class.  Construction and destruction.
protected:
    zelObject ();
public:
    virtual ~zelObject ();

};
Como ves el objeto Rtti se declara como static por que no hay necesidad de repetir esta información en cada instancia. Como cada clase tiene su propio objeto Rtti, el constructor escribirá en su propia instancia estatica el nombre de la clase. Para ayudar a que la declaracion sea menos tediosa incluyo estas macros:
//----------------------------------------------------------------------------
#define RTTI_DECLARATION \
public: \
    static const Rtti TYPE; \
    \
    virtual const Rtti& GetRttiType () const \
    { \
        return TYPE; \
    }
//----------------------------------------------------------------------------
#define RTTI_IMPLEMENTATION(nsname, baseclassname, classname) \
    const Rtti classname::TYPE(#nsname"."#classname, &baseclassname::TYPE)
//----------------------------------------------------------------------------
que delcaran el Rtti en cada clase como public(para sobreescribir en cada clase la declaración) e inicializar la variable estatica on el nombre de clase con su namespace, y el tipo. Pero para que tanto rollo? Entre otras cosas para poder hacer dynamic_cast sin tener que activar el rtti. Y que consigo con esto? Pues mi siguiento paso es la serialización, pero esto ya lo contaré más adelante.

jueves, 23 de febrero de 2012

La pila

Si quieres sentir la libertad de programar en C/C++ y tener control sobre todos los recursos de la aplicación, debes aceptar la responsabilidad de conocerlos por que sino las consecuencias pueden ser bugs escurridizos de verdad. Mira este:

#include <iostream>
#include <sstream>

using namespace std;
unsigned int* theFunction(unsigned int b)
{
 unsigned int a = 33 + b;
 return &a;
}

std::string getMessage(int a)
{
 string bb = "El valor de la variable a es: ";
 stringstream ss;
 ss << bb << a << "\n";
 return ss.str();
}

int main()
{
 int mi_variable = 40;
 unsigned int* a = theFunction(mi_variable );
 if(*a > 55)
 {
  string msg( getMessage(*a));
  cout << msg << endl;
 }
 cout << "El resultado es "<< 31 + *a << endl;
 system("PAUSE");
 return EXIT_SUCCESS;
}

Si lo compilas y lo pruebas no fallará, pero si le cambias e valor a mi_variable y juegas un poco con su valor al final fallará algo. Pero que ha pasado?
Cuando un programa un programa se inicia, reserva un trozo de memoria que se llama “pila de programa” (programa stack). Para que se usa? Cada vez que un programa llama a una función, el programa usa un trozo de memoria contigua (llamado stack frame), y en ella coloca lo que necesita para que la función se ejecute: por un lado alli pone la direccion de retorno, o sea, la dirección donde el programa debe ir cuando se acabe la función. Por otro lado reserva espacio para las variables que se usan en la función. En tercer lugar, salva el contenido de los registros del procesador que sean relevantes. Por ejemplo, en la siguiente función:

int f()
{
 unsigned int a = 0;
 double b = 1.00;
 g();
 return a;
}
el sistema reservaria un espacio para guardar la dirección de retorno (seguramente una palabra de memoria, de 32bits en SO de 32bits), otra para palabra para el unsigned int y dos palabras para el double. Además tambien guardaria los registros necesarios para continuar la ejecución del código que llama a f() una vez finalice. Que pasa con g(), pues que el sistema vuelve a usar otro trozo de memoria de la pila para ejecutar g(). Cuando acaba g() vuelve a f().

El valor que retorna f() se guarda en un registro para que esté alli cuando el código que llamó a f() se continúe ejecutando. Esta estructura es muy conveniente para el sistema. Por un lado no tiene que buscar un trozo de memoria conveniente sino que simplemente usa el siguiente al que tiene. Además, cuando acaba con la función simplemente abandona el trozo de memoria usado sin hacer ninguna limpieza, con lo que se gana velocidad y simplicidad a partes iguales.

Entonces, que ha pasado en el código del principio?
En el momento en que se sale de la función, esa zona de la memoria queda “abandonada” pero el valor de “a” no se pierde por que el sistema no hace ninguna limpieza. Si mi_variable es menor que 22, no pasa nada por que no entramos en el “if”, y por tanto no llamamos a la función getMessage. Asi que el valor final es correcto. En cambio, a partir de 22, entramos en el if y por tanto llamo a getMessage. Eso provoca que el sistema use el espacio contiguo de memoria, donde estaba el valor de a, y sobreescribe, “pisando” el valor de “a”.

Lo peor de todo es que no da ninguna excepción (en el momento de la compilación tira un warning que, como siempre, no deberías ignorar), pero nos mostrará el valor que tenga la memoria en ese momento que puede ser cualquiera, provocando un bug que puede llegar a ser escurridizo como él solo. Asi que cuando juegues con c++, recuerda que un gran poder conlleva una gran responsabilidad (o diversión, siempre que te guste cazar bugs, claro :) )

Espero que con esta entrada hayas entendido algo más sobre la pila. En otra entrada hablaremos un poco sobre el heap, o montón.

lunes, 20 de febrero de 2012

Programacion orientada a tests (TDD)

Como estoy volviendo a programar sobre mi motor y he eliminado un montón de código, ahora tengo que repetir algunas clases y añadir otras. He estado mirando algunos paradigmas de programación y hay uno que llamó poderosamente mi atención que es la programacion orientada a test o “test driven development”. 

Hace un tiempo publiqué una entrada sobre las bondades de los test unitarios, pero la verdad es que cuando profundizas en ellos, te acabas preguntando indefectiblemente: “para que quiero hacer test sobre un código que ya esta acabado?” y tambien uno se pregunta “si ya se como funciona ese metodo (por que yo mismo lo he programado), por que voy a probarlo?” También hay gente que se pregunta si no es una herramienta de calidad usada por programadores. He invertido algún tiempo en leer cosas sobre el tema y puedo decir que no. TDD es una herramienta que abarata costes de producción porque reduce los bugs y sobretodo el tiempo que tardamos en encontrarlos. Pero ademas sirve para entender el código. Nunca te has bajado un código que no entiendes? Sabes que si lo tocas en el punto A vas a romper algo en el punto B (donde B puede ser cualquier lugar). Una manera de ahcer cambios en el código es escribir test unitarios para ver si realmente entendemos lo que hacemos. Si falla es que no lo entendemos. Si sale verde es que entendemos esa pieza de código.

En TDD debemos diseñar, escribir los tests (testear) y despues desarrollar. Los pasos serian:

  1. Escribimos el test case unitario 
  2. Lanzamos los test unitarios escritos hasta ahora 
  3. Si sale verde (todos los test OK), no hacemos nada más. Nuestro trabajo ha acabado a menos que salgan nuevos requisitos 
  4. Si sale rojo aplicamos la solución más sencilla que se nos ocurra y nos lleve al verde. 
  5. Volvemos al punto 2. 


Es importante señalar que en este proceso solo podemos estar haciendo una de estas 3 cosas: programar tests unitarios nuevos, programar funciones que satisfagan los test unitarios, haciendo refactoring para reducir duplicidad de código. Este último punto se da por la regla de programar siempre la solución más sencilla. Recuerda que cada vez que tocamos código debemos lanzar otra vez nuestro conjunto de test unitarios. Un ejemplo: imaginemos que tenemos que hacer parte de una calculadora donde entra un string con una operación matemática y tu devuelves un int que representa el resultado de la operacion o lanzas una excepción en caso de error. Es válido estas operaciones:
“1 + 2” y devolvemos 3
“5 - 3” y devolvemos 2
“3” y devolvemos 3
“” y devolvemos 0

Este ejemplo lo he sacado de aqui: http://sajdak.eu/trainings/agile-cpp-developer/google-test/first-and-easy-but-real-test/ Antes de seguir leyendo, intenta pensar en como programarias una función asi: Lo primero que hacemos es crear uno de los test cases unitarios
TEST(text_calculator,001_empty_string_returns_zero)
{
	CText_calculator tc;
	ASSERT_EQ(0, tc.calculate(""));
}
Ejecutamos los test y ni siquiera compilan, por que no existe la funcion. Pero ya hemos ejecutado y cada ejecución nos puede servir de barrera para “cambiar de sombrero” de programador de aplicacion a programador de test. Asi solo tocamos una cosa cada vez. Recuerda que la idea es implementar la idea más sencilla que se nos ocurra.
#pragma once
#include "string"

class CText_calculator
{
public:
	int calculate(const std::string& op)
	{
		return 0;
	}
	CText_calculator(void){}
	~CText_calculator(void){}
};
Volvemos a ejecutar los test cases y … “verde!!!” Ya cumplimos el primer requisito. Vamos a escribir otro test con el segundo requisito
TEST(text_calculator, cadena_vacia_devuelve_cero)
{
	ASSERT_EQUALS(text_calculator(“3”), 3);
}
Ejecutamos y evidentemente los tests fallan.
Running main() from gtest_main.cc
[==========] Running 2 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 2 tests from text_calculator
[ RUN      ] text_calculator.001_empty_string_returns_zero
[       OK ] text_calculator.001_empty_string_returns_zero (0 ms)
[ RUN      ] text_calculator.002_literal_3_returns_3
c:\directo\mypgp\blog_to_tes\text_calculator_test\text_calculator_test\test_case
s.cpp(13): error: Value of: tc.calculate("3")
  Actual: 0
Expected: 3
[  FAILED  ] text_calculator.002_literal_3_returns_3 (0 ms)
[----------] 2 tests from text_calculator (0 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 1 test case ran. (0 ms total)
[  PASSED  ] 1 test.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] text_calculator.002_literal_3_returns_3

 1 FAILED TEST
Presione una tecla para continuar . . .
Falla cocretamente el test del 3. Vamos a meter unos if
	int calculate(const std::string& op)
	{
		if(op == "")
			return 0;
		if(op == "3")
			return 3;
	}
Al ejecutar, todos los test van bien. Pero como el requisito no es meterle “3” sino cualquier numero, vamos a crear un test mas amplio:
TEST(text_calculator,003_literal_always_return_the_number_that_represents)
{
	CText_calculator tc;
	srand(time(NULL));
	int generated = rand() % 1000 + 1;
	std::stringstream out;
	out << generated;

	ASSERT_EQ(generated, tc.calculate(out.str()));
}
Ejcutamos el test y falla. Además vemos que con un if no vamos a ninuna parte, asi que modificamos el código de otra manera:
#pragma once
#include "string"
#include "sstream"

class CText_calculator
{
public:
	int calculate(const std::string& op)
	{
		if(op.size() == 0)
			return 0;
		
		int result;
		std::stringstream(op) >> result;
		return result;
	}

	CText_calculator(void){}
	~CText_calculator(void){}
};

Ahora volvemos a ejecutar y todo va bien, da igual el número que le metamos. Vamos ahora con las operaciones de dos operandos. Lo primero, escribir el test correspondiente:
TEST(text_calculator,004_3_plus_5_always_returns_6)
{
	CText_calculator tc;
	ASSERT_EQ(8, tc.calculate("3 + 5"));
}
Para resolverlo puedo se me ocurre que primero podria mirar si hay un ‘+’ y entonces ya sabria que es una operacion de dos operandos:
#pragma once
#include "string"
#include "sstream"

class CText_calculator
{
public:
	int calculate(const std::string& op)
	{
		if(op.size() == 0)
			return 0;
		
		int result;
		std::stringstream ss(op);
		ss >> result;

		size_t operator_pos = op.find("+");
		if(operator_pos == std::string::npos)
			return result;
		std::string second_op(op.substr(operator_pos + 1));
		int result2 ;
		std::stringstream ss2(second_op);
		ss2 >> result2;
		return result2 + result;


	}

	CText_calculator(void){}
	~CText_calculator(void){}
Los test pasan perfectamente pero veo varias cosas. La primera que el codigo de convertir strings a int se repite, y el codigo repetido casi siempre hay que evitarlo. Vamos a hacer refactoring. Este es otro gran uso del TDD. Si primero, antes de hacer refactor, escribimos los test y nos aseguramos que pasan, despues solo tenemos que ejecutar los test cases despues de cada cambio del refactoring para darnos cuenta si falla algo. El resultado de separa la función de pasar de string a numero es esta:
#pragma once
#include "string"
#include "sstream"

class CText_calculator
{
private:
	int str_to_number(const std::string& str)
	{
		int result;
		std::stringstream ss(str);
		ss >> result;
		return result;
	}
public:
	int calculate(const std::string& op)
	{
		if(op.size() == 0)
			return 0;
		
		int number1 = str_to_number(op);

		size_t operator_pos = op.find("+");
		if(operator_pos == std::string::npos)
			return number1;

		std::string second_op(op.substr(operator_pos + 1));
		int number2 = str_to_number(op.substr(operator_pos + 1));
		return number1 + number2;
	}

	CText_calculator(void){}
	~CText_calculator(void){}
};
Ahora ya puedo escribir otro caso de test:
TEST(text_calculator,004_5_minus_3_always_returns_2)
{
	CText_calculator tc;
	ASSERT_EQ(2, tc.calculate("5 - 3"));
}
Y al ver que falla aplicar los cambios: hay varios problemas que quiero solventar. Por un lado falla al reconocer el simbolo, y por otro lado puede que no acepte combinaciones raras tipo “4+”, “+5”, “+”,”hfds+3”. La primera parte se puede solucionar dividiento el string en tres partes: operando, simbolo, operando. Para ello uso un boost::tokenizer y cambio la manera de enfocar las operaciones:
#pragma once
#include "string"
#include "sstream"
#include "boost/tokenizer.hpp"

class CText_calculator
{
private:
	int divide_string(const std::string& op,std::string& op1, std::string& simbolo, std::string& op2)
	{
		typedef boost::tokenizer > tokenizer;
		boost::char_separator sep("-+");
		tokenizer tokens(op,sep);
		tokenizer::iterator tok_iter = tokens.begin();
		op1 = *tok_iter++;
		if(tok_iter != tokens.end())
			op2 = *tok_iter;
		else
			return 1;
		simbolo = op.substr(op1.size(), 1);
		return 2;
	}
	int str_to_number(const std::string& str)
	{
		int result;
		std::stringstream ss(str);
		ss >> result;
		return result;
	}
public:
	int calculate(const std::string& op)
	{
		if(op.size() == 0)
			return 0;
		
		std::string op1, op2, symbol;
		int result = 0;
		if ( result = divide_string(op, op1, symbol, op2) == 1)
			return str_to_number(op1);
		if (symbol == "+")
			return str_to_number(op1) + str_to_number(op2);
		if (symbol == "-")
			return str_to_number(op1) - str_to_number(op2);
	}

	CText_calculator(void){}
	~CText_calculator(void){}
};
Añadir las dos operaciones que faltan es trivial. Solo hay que añadir los dos simbolos que faltan a la lista de separator char y añadir los if correspondientes. Añado primero los test:
TEST(text_calculator,005_10_mult_10_always_returns_100)
{
	CText_calculator tc;
	ASSERT_EQ(100, tc.calculate("10 * 10"));
}

TEST(text_calculator,006_15_divided_3_always_returns_5)
{
	CText_calculator tc;
	ASSERT_EQ(5, tc.calculate(" 15 / 3 "));
}
Ejecuto y aplico cambios. Recuerda que después de decir que el cambio era trivial, he escrito los tests y los he ejecutado. Parece paranoico pero es lo mejor por muchas razones. Primero por que la próxima vez que alguien le meta mano al código, ya están hechos los tests. Segundo por que un mal click puede introducir un simbolo incorrecto y luego ese error trivial convertirse en un error oscuro y difícil de encontrar dentro una aplicación mas grande El código final es este:
#pragma once
#include "string"
#include "sstream"
#include "boost/tokenizer.hpp"

class CText_calculator
{
private:
	int divide_string(const std::string& op,std::string& op1, std::string& simbolo, std::string& op2)
	{
		typedef boost::tokenizer > tokenizer;
		boost::char_separator sep("-+*/");
		tokenizer tokens(op,sep);
		tokenizer::iterator tok_iter = tokens.begin();
		op1 = *tok_iter++;
		if(tok_iter != tokens.end())
			op2 = *tok_iter;
		else
			return 1;
		simbolo = op.substr(op1.size(), 1);
		return 2;
	}
	int str_to_number(const std::string& str)
	{
		int result;
		std::stringstream ss(str);
		ss >> result;
		return result;
	}
public:
	int calculate(const std::string& op)
	{
		if(op.size() == 0)
			return 0;
		
		std::string op1, op2, symbol;
		int result = 0;
		if ( result = divide_string(op, op1, symbol, op2) == 1)
			return str_to_number(op1);
		if (symbol == "+")
			return str_to_number(op1) + str_to_number(op2);
		if (symbol == "-")
			return str_to_number(op1) - str_to_number(op2);
		if (symbol == "*")
			return str_to_number(op1) * str_to_number(op2);
		if (symbol == "/")
			return str_to_number(op1) / str_to_number(op2);
	}

	CText_calculator(void){}
	~CText_calculator(void){}
};
Todavia podriamos aplicar un refactor con todos esos “if” de la funcion publica, y para ello los test automáticos que hemos hecho nos ayudarian también. Si piensas que escribir los tests cases llevan mucho tiempo, piensa que en total no me ha llevado mas de 3 o cuatro minutos (son todo copy/paste). A cambio, la aplicación sale con un grado de madurez medio bueno, y eso que en realidad no he hecho todos los test que se me puedan ocurrir. Todavia quedarian por añadir casos algo más extremos como:
TEST(text_calculator,007_tests_with_wrong_values)
{
	CText_calculator tc;
	ASSERT_EQ(0, tc.calculate(" 1assadf / 3 "));
}
TEST(text_calculator,008_tests_with_wrong_values_2)
{
	CText_calculator tc;
	ASSERT_EQ(0, tc.calculate(" 15 / sadas3 "));
}
Y descubrir que este, por ejemplo, falla
TEST(text_calculator,009_tests_with_wrong_values_3)
{
	CText_calculator tc;
	ASSERT_EQ(0, tc.calculate(" 15  16/ 3 "));
}
Y deberia aplicar cambios, pero creo que a partir de ahi ya ves por donde va el tema. Espero que este articulo te haya demostrado como el TDD puede ayudarte a escribir mejor código a la vez que acelera el ritmo de produccion al evitar que los bugs se escondan en tu código durante demasiado tiempo. Si tienes alguna pregunta no dudes en postear.

jueves, 20 de octubre de 2011

Una entrada interesante sobre Boost.Serialization

Me ha interesado mucho este articulo en IBM.com sobre Boost.Serialization. Basicamente esta libreria te permite convertir un objeto en un chorro de bytes (y viceversa), permitiendote enviar objetos por la red o guardarlos en un archivo. Es ideal para ayudar a un desarrollador a crear partidas salvadas y cargarlas luego, o para crear partidas en red.

El articulo es en ingles, o sea, el idioma de la informatica y de internet (yo solo lo dejo ahi... para que lo pienses.... :) )

Espero que te guste.

jueves, 13 de octubre de 2011

Apuntes de Multithreading (II)


En este segundo capitulo pretendo mostrar como se crean threads. Un thread, como expliqué en el anterior artículo, es un objeto de la clase boost::thread, asi que al crearlo se llama al constructor.

Al crearlo, como parámetro de entrada le damos una función que es la que ejecutaremos en un thread separado.

void func1()
{
    //hacer algo aqui
}
int main()
{
    boost::thread t(func1);
    t.join();
}


Aunque tambien podemos usar functors para indicar al thread que tarea ejecutar.

class complexFunc2
{
    void operator()() const
    {
        //hacer algo complejo aqui.
    }  
};

int main()
{
    boost::thread t(complexFunc2);
    t.join();
}


Hay algo que es importante. El parametro que le pasamos a un thread entra por copia y no por referencia. Una vez finaliza el thread, el objeto que le hemos pasado por copia es destruido. Esta operacion no tiene ningún peligro excepto si entre tus datos miembro tienes punteros, y el destructor los intenta borrar, asi que cuidado.

Por ejemplo:

class complexFunc3
{
    int& number;
    complexFunc3(int& _number):number(_number){}
    void operator()() const
   {
     for(int i = 0; i < 10000; i++)
       printf("%d\n",number);
   }
};
int main()
{
    int myVar = 2;
    boost::thread t(complexFunc2(myVar));
}


Una vez acaba el main, el thread principal (nuestro programa principal siempre es un thread) acaba y libera recursos. En este caso myVar es destruida. Probablemente en una de las iteraciones del bucle, printf fallará porque myVar ha dejado de existir, y por tanto number es una referencia a un número que ya no existe, y fallará.

Para evitarlo deberiamos llamar al método join(), para que el main espere la finalización de thread antes de salir. El join deberia ser llamado si al final de una funcion o dentro de la excepcion. Esto es:

int main()
{  
    int myVar = 2;
    boost::thread t(complexFunc2(myVar));

    try{
        // Algunas operaciones que pueden gnerar excepcion.
    }
    catch()
    {
        t.join();
        throw;
    }
    t.join();
}


Nos puede ser útil tambien hacer lo contrario y es separar definitivamente el thread del principal. Para ello primero debemos comprobar que hay un thread para separar, o para juntar en caso de que vayamos a usar join. Para comprobarlo usamos t.joinable(), y si sale true es que podemos hacer un detach().

int main()
{  
    boost::thread t(func);
    if(t.joinable())
        t.detach();
}
Por ultimo, explico que pasar parámetros a un thread es trivial.
void func(int i, float f){}
int main()
{  
    boost::thread t(func,2,2.5f);
    t.join();
}


Pero no hay que olvidar que los parametros se copian y que al final de la ejecución son destruidos asi que hay que tener en cuenta que enviamos y que pasará cuando sean destruidos, o que pasará cuando la función que crea el thread finalice y destruya las funciones creadas alli y el thread siga en marcha.

Pues aqui llega el final de este post, aunque todavia me queda un largo camino con los threads.

lunes, 10 de octubre de 2011

Apuntes de Multithreading

Probablemente te vas a aburrir con esta entrada, pero no pretendia ser eso sino más bien una especie de apuntes por que estoy aprendiendo desde 0 un poco de multithreading en C++.  En realidad queria escribir algo de boost::thread, pero al final se ha juntado todo un poco...

En principio habria que saber que un thread es un proceso que se ejecuta paralelamente a la aplicacion. Desde el punto de vista de Boost (aunque dentro de poco será std::thread), un thread es una clase, que podemos crear cuando queramos. Si tenemos instalado boost como libreria global a todo el sistema, no necesitamos nada para compilarlo. El código seria este:

#include "iostream"
#include  "boost/thread.hpp"

void hola()
{
    std::cout<<"Hola mundos paralelos!!!\n";
}

int main()
{
    boost::thread t(hola);
    t.join();
}

Si esto te compila y ejecuta sin quejarse es que tienes bien instalado Boost. ¿Pero funciona realmente?
Vamos a probar esto:


#include "iostream"
#include  "boost/thread.hpp"

const unsigned int MAX = 1000;
void hola()
{
    for(unsigned int i = 0; i < MAX; ++i)
    {
    	std::cout << "Mensaje dentro de la funcion HOLA numero " << i << "\n";
    }
}
int main()
{
    boost::thread t(hola);
    for(unsigned int j = 0; j < MAX; ++j)
    {
    	std::cout << "Mensaje desde el main con numero " << j << "\n";
    }
    t.join();
}
Si redireccionas la salida a un archivo, por que son 2000 lineas, tendrás que las lineas no salen separadas. Puedes encontrar lineas como esta:
Mensaje desde el main con numero Mensaje dentro de la funcion HOLA numero 13
Mensaje desde el main con numero 1014
Los carácteres entran cuando pueden, aunque pise media frase en el camino del otro thread.

Y que hace este código? Pues primero creamos un objeto thread. Como parámetro del constructor, le pasamos la función que queremos ejecutar. Luego viene el bucle que escribe mensajes en el thread principal, aunque dentro del thread tambien se estan enviando mensajes. Una vez acaba el bucle principal, si no hubiera nada más, el programa principal acabaria y el thread se quedaria alli haciendo lo que sea que tiene que hacer.

Para decir al programa que cierre el thread antes de irse, llamamos a join() que es como pedirle al programa que espere en ese punto al thread antes de continuar, y despues continue.

Y asi acaba la primera entrada sobre threads.