FTP | CCD | Buscar | Trucos | Trabajo | Foros |
|
Registrarse | FAQ | Miembros | Calendario | Guía de estilo | Temas de Hoy |
|
Herramientas | Buscar en Tema | Desplegado |
#1
|
||||
|
||||
Cosas que nunca debe hacer con C++ Builder
(Ver rectificación al final)
Sinceramente, estoy sorprendido. Los ejemplos que he estado mirando de C++ Builder en estos días, tanto en los manuales de Inprise como en libros que hay por ahí, son como para no poder dormir. ¿El motivo? La falta de atención que le prestan al tratamiento de excepciones. Por ejemplo, este método pertenece a la famosa MASTAPP, versión C++: Código:
void TMastData::DeleteItems() { DeletingItems = True; // Suppress recalc of totals during delete Items->DisableControls(); // for faster table traversal. try { Items->First(); while(!Items->Eof) Items->Delete(); } catch(...) { DeletingItems = False; Items->EnableControls(); //always re-enable controls after disabling return; } DeletingItems = False; Items->EnableControls(); //always re-enable controls after disabling } El error consiste en llamar a return dentro de catch. ¿Sabe lo que logra con esto? Pues que la excepción ha quedado "atrapada" y muere. El usuario nunca se entera de que ha sucedido un error. Pero no es esto lo peor. ¿Sabe usted desde dónde se va a llamar a DeleteItems? Yo no lo sé; puede que en algún caso particular usted lo sepa, pero es mala programación asumir estas dependencias dentro de un programa controlado por eventos. Un método manejador de eventos (no es éste el caso) tiene la siguiente particularidad: después de terminar la parte visible de su ejecución, el contador de instrucciones sigue moviéndose por el código interno de la VCL, y ahí usted no tiene control de todo lo que sucede. ¿Sabe el peligro a que se expone cuando deja que la VCL asuma que no hubo errores dentro de la respuesta al evento? La Tercera Regla de Marteens dice: "Nunca mates una excepción para la cual no tengas una solución". El mal aprovechamiento de recursos se refiere a que las dos instrucciones finales se repiten, tanto en el catch como después del catch. Esto sucede porque C++ puro y duro no tiene la instrucción try/finally de Delphi y Java ... y de C sin los dos signos de adición. ¿Qué por qué me molesta la repetición de código? Lo de menos es que el programa sea un poco más grande. Lo de más es que cuando tengamos que modificar algo en esas dos instrucciones, tendremos que trabajar por duplicado ... y existe el riesgo de que ambos bloques pierdan la sincronía. ¿Qué hubiera hecho yo? Si me interesara ajustarme estrictamente a lo que permite el estándar de C++, hubiera sustituido, en primer lugar, el return por un throw (el raise de Delphi) para relanzar la excepción: Código:
catch(...) { DeletingItems = False; Items->EnableControls(); throw; // Esto es diferente } DeletingItems = False; Items->EnableControls(); Código:
void TMastData::DeleteItems() { DeletingItems = True; // Suppress recalc of totals during delete Items->DisableControls(); // for faster table traversal. try { Items->First(); while(!Items->Eof) Items->Delete(); } __finally { DeletingItems = False; Items->EnableControls(); //always re-enable controls after disabling } } De todos modos, una de las causas más extendidas en Delphi y C++ Builder que hacen necesario el uso de try/finally es la creación de objetos dinámicos locales a un método. Una idea que se me ocurre es utilizar una técnica bastante extendida entre los programadores de C++: los punteros inteligentes, o smart pointers. He visto esta técnica en C++ Builder en relación con las interfaces para la programación COM, pero no he encontrado la correspondiente aplicación a la programación con la VCL día a día (si Borland la ha implementado o recomendado, debe haber escondido bastante bien la recomendación). Observe la siguiente plantilla de clase: Código:
template<class T> class LocalVCL { private: T* Instance; public: LocalVCL(T* t) : Instance(t) {} LocalVCL() { Instance = new T(NULL); } ~LocalVCL() { delete Instance; } T* operator->() { return Instance; } operator T* () { return Instance; } }; Para redondear la clase, definimos el operador ->, para que cuando lo apliquemos a una variable de tipo LocalVCL devuelva el objeto al cual apunta la instancia que contiene. El otro operador se encarga de las conversiones de tipo, desde un LocalVCL al componente de la VCL asociado. En todos los casos, las definiciones de los métodos son inline, para evitar código innecesario. En el siguiente método vemos en acción a LocalVCL en dos situaciones diferentes: con un componente (el diálogo de configuración de la impresora) y con un objeto gráfico (un mapa de bits). En el caso del mapa de bits tenemos que utilizar el primer constructor, y construir explícitamente el objeto, ya que el constructor de la clase TBitmap no admite parámetros. En cambio, la construcción del diálogo es más sencilla y compacta, por tratarse de un componente: Código:
void __fastcall TForm1::Button1Click(TObject *Sender) { LocalVCL<Graphics::TBitmap> B = new Graphics::TBitmap; LocalVCL<TPrinterSetupDialog> PS; PS->Execute(); B->Width = Screen->Width; B->Height = Screen->Height; } Código:
void __fastcall TForm1::Button1Click(TObject *Sender) { Graphics::TBitmap* B = new Graphics::TBitmap; try { TPrinterSetupDialog *PS = new TPrinterSetupDialog(NULL); try { PS->Execute(); } catch(...) { delete PS; throw; } delete PS; B->Width = Screen->Width; B->Height = Screen->Height; } catch(...) { delete B; throw; } delete B; } POST SCRIPTUM: C++ Builder sigue manteniendo la definición de una "vieja conocida", la plantilla auto_ptr, que está definida en <memory>. A grandes rasgos, realiza la misma tarea que mi LocalVCL, pero soporta más operadores. El siguiente ejemplo muestra cómo puede utilizarse auto_ptr: Código:
void __fastcall TForm1::Button1Click(TObject *Sender) { // Observe que la sintaxis de la construcción es diferente auto_ptr<Graphics::TBitmap> B(new Graphics::TBitmap); auto_ptr<TPrinterSetupDialog> PS(NULL); PS->Execute(); B->Width = Screen->Width; B->Height = Screen->Height; } |
|
|
|