lunes, abril 14, 2008

C--

No se si en realidad estoy buscando una excusa para descargar mi frustración de esta semana, o si es más bien porque Pablo me sugirió que para facilitar nuestra comunicación (en el ámbito de la programación) mejor usara C, o porque quiero usar SWIG, que no soporta templates, o porque quería usar wxWidgets y no importaba que hiciera siempre me reportaba el “falta el archivo cc1”...

Como sea que fuere, estoy cada vez más molesto con algunas cosas de C++. Es cierto, el lengujae tiene muchas cosas que son brutales. Para mi la principal es el encapsulamiento explicito de los datos, pero de las otras cosas...

Por un lado estan los operadores sobrecargados, yo al principio me emocione mucho, pero ya después uno comienza a descubrir que quizá sea más optimo usar las tradicionales funciones. Por ejemplo, yo tengo un campo de bits:

class BitField {
public:
BitField(int b=0);
BitField(const BitField&);
private:
unsigned bits;
};

Para los que no estén familiarizados con C++, las funciones públicas son dos constructores, uno que modifica el constructor por omisión y el otro un constructor copia. En la parte privada están los bits del campo de bits.

Entre otras cosas yo quiero que el objeto b, sea el inverso del objeto a. Para ello puedo hacer un operador:

friend BitField& operator~(const BitField&);

Aquí esta parte del asunto, el operador es amigo porque la idea es formar un nuevo objeto que tenga todos los valores de a inversos, pero manteniendo a a intacto.

BitField& operator~(const BitField& a) {
BitField b=a;
b.bits=~a.bits;
return b;
}

Aquí se debe construir una copia de a, luego, bien sea cada elemento de a, o de b es invertido. Lo adecuado debería haber sido de una vez copiar en b los inversos de a, estamos haciendo una operación redundante. Más aun si después de eso tenemos que asignar el resultado:

BitField& operator=(const BitField&); //Esto se declara en la parte pública de BitField

BitField& BitField::operator=(const BitField& b) {
//Aquí va una limpieza de todo lo que hay en el objeto actual
bits=b.bits; //Y esta es la asignación
}

De nuevo otra copia, es decir, hemos copiado 3 veces cuando solo una vez era suficiente! Quizá con problemas pequeños no pasa nada, pero con las filogenias (árboles) cuya construcción suele ser un problema NP-completo, es urgente evitar redundancia de evaluaciones y así ahorrar tiempo que puede ser usado para probar otras combinaciones.

Usando una filosofía de no-operadores la cosa seria instantánea:

void add_inverse(const BitField&); //Esto se declara en la parte pública de bitField

void add_inverse(const BitField& a) {
//Aquí va una limpieza de todo lo que hay en el objeto actual
bits=~a.bits; //Y esta es la asignación
}

La situación es equivalente usando suma, resta, o las otras operaciones, las únicas realemente optimas son +=,*=, etc. pues estás asignan directamente al objeto que estemos trabajado, eludiéndose las copias redundantes.

Una solución podría ser tener un enum, con las operaciones y ponerlo en la declaración, del constructor copia o del de asignación. Más o menos así:

enum Operations {
NONE, NEGATION, SUM, ...
};

//Constructores copia
BitField(const BitField&, Operations op=NONE); //Para operadores unitarios
BitField(const BitField&, const BitField&, Operations op=SUMA); //Operadores duples
//Funciones de asignación
bool assign(const BitField&, Operations op=NONE);
bool assign(const BitField&, const BitField&, Operations op=NONE);

Despues simplemente en cada función hacemos un switch(op) para determinar que hacemos, genera funciones más grandes, pero si tenemos varias operaciones, podemos siempre llamar a la función, y evitamos tener mil funciones que hacen más o menos lo mismo.

No se si para los constructores se puedan usar punteros a funciones pero para las funciones de asignación esa sería una segunda alternativa.

Si algún guru de C++ (en realidad, cualquier usuario) que tenga una solución a ese problema usando operadores, me gustaría saber su idea :).

Ahora pasemos al problema de las librerías “estándar” de C++. Yo uso dos compiladores, así que bien podría ser 'culpa' de ellos más que del estándar. Uno es gcc (MinGW), que produce unos archivos enormes, como IDE uso Dev-C++, que es fantástico, aunque su versión anterior, la 4.1, creo, el depurador funcionaba mucho mejor que en la actual (la 4.9.9.9....9), es decir, funcionaba, ahora, no importa que haga, el depurador nunca a vuelto a funcionar :( porque falta 'información' de depuración! Un depurador fantástico lo tiene OpenWatcom, y por eso es mi segundo compilador... pero OpenWatcom no usa C++ estándar :S... lo cual me genera algunos problemas para moverme entre plataformas, y teniendo en cuenta que quiero tener programas que compilen en windows y en linux (es decir, con gcc).

Las funciones de la librería “estándar” de OpenWatcom, parece más una forma tipo C de usar C++ (lo que yo quiero hacer ahora), no usa templates, y los archivos finales son más chicos. Pero algunas clases tienen nombres diferentes, y otros servicios, por ejemplo, en gcc, uno usa ostringstream, la función str() devuelve un string (la clase de la librería estándar), mientras que su equivalente en OpenWatcom (ostrstream) regresa un char* que además no termina en '\0'. La solución es muy fea, en un archivo de definciones, por ejemplo “my_def.h”:

#ifdef GCC_COMPILER
#include
typedef std::ostringstream OutString;
typedef std::istringstream InString;
#endif
#ifdef OPENWATCOM
#include
typedef std::ostrstream OutString;
typedef std::istrstream InString;
#endif

Y en el código fuente, agregar el '\0' antes de llamar str():

OutString ost;
#ifdef OPENWATCOM
ost(menorque)(menorque)'\0';
#endif
return ost.str();

Hasta ahí las cosas van bien.. pero por ejemplo, en OpenWatcom, uno no puede usar un objeto string para inicilizar InString! Peor aún, los strings de openwatcom solo devuelven char const*, que se muestran fuertemente reacios a ser movidos a un char*, probé con un strcpy(), pero uno de los motivos para usar string es no tener que depender de las funciones de C donde uno debe tener limites iniciales fijos del string... Después de dar un millón de vueltas, llegue a esta solución, un poco molesta, pero funcional:

#ifdef OPENWATCOM
char* ts;
OutString ost;
ost(menorque)(menorque)s(menorque)(menorque)'\0';
ts=ost.str();
InString in(ts);
#endif


... Y después de todo eso, compilo mi programa, con el mismo conjunto de datos, de repente el compilado con gcc se cae, a veces si, a veces no, el compilado con Watcom, esta perfecto, pero si corro otro conjunto zaz! Muerte súbita... parece que hay un leak en el manejo de memoria, pero el crash no debería pasar, la versión anterior que no usaba delete (esa si que tenía leaks de memoria!) funcionaba bien... es muy raro! Así como mi instalación de wxWidget, después de probar mil y un alternativas (los makefiles que vienen, ninguno funciono :(, ni los de watcom, ni los de MinGW, baje entonces una serie de paquetes para Dev-C++, que tienen las librerías precompiladas, seguí las instrucciones, y ahora gcc llama a un 'cc1', en el faq no dice nada de ese error, creo que tendre que buscar en el foro xD, después instale wxDev, que es la versión de Dev-C++ con wxWidget integrado y zaz, de nuevo, el mismo error (y una interfaz 10 veces más lenta). Como dice Loló, que bajón!

Y Pablo pues, el a vivido siempre con C, y hace cosas brutales con el (cualquiera que haya corrido TNT lo va a notar!), y pues para mi seria mejor poder mostrarle mi código a mi director, y que el lo pueda entender sin problemas. Quizá utilice una programación tipo C++ con C, es decir, yo mismo me pongo el requerimiento de mantener ocultos los datos xD. Creo que eso lo puedo hacer usando clases normales, mantener todas las operaciones lo más C posible (no referencias, no operadores, no librería estándar, no herencia--que igual, casi nunca uso--usar malloc y free para manejar memoria? Bueno creo que const si forma parte del C estándar, y la sobrecarga de funciones, y los parámetros por omisión son ideas que seguro Pablo va a entender de una vez, y quizá las use con C xD. Se me ocurre colocar todo en una clase, poner solo funciones friend, y después cuando todo funcione, cambiar de class a struct y borrar la declaración de los friend (dentro del nuevo struct).

No se, lo pensare en estos días mientras logro solucionar mis leaks de memoria xD...

Pero bueno, sirvió el desahogo :). Por si algún amigo alcanzo a llegar hasta aquí, cosa que dudo xD, pues me pagaron, pero solo la primera semana de febrero, supuestamente ya hoy debería tener lo de marzo, ya veremos :P.

Side-note: que post más largo y más geek xD jajajaja...

Addendum
Ya solucione mi leak de memoria :), aún así, decidí eliminar buena parte de mis operadores...

3 comentarios:

Eri dijo...

Ay salva, pues que bueno lo del pago y pues ánimo con su programa :)

Anónimo dijo...

Hurrah, that's what I was seeking for, what a material! existing here at this web site, thanks admin of this website.

My web blog - www.ifnipedia.es

Anónimo dijo...

I don't even know how I ended up here, however I thought this put up was once great. I do not realize who you're however definitely you're going to a well-known blogger should you are not already. Cheers!

my page :: natural cellulite treatment