Club Delphi  
    FTP   CCD     Buscar   Trucos   Trabajo   Foros

Retroceder   Foros Club Delphi > Otros temas > Trucos
Registrarse FAQ Miembros Calendario Guía de estilo Buscar Temas de Hoy Marcar Foros Como Leídos

Los mejores trucos

Respuesta
 
Herramientas Buscar en Tema Desplegado
  #1  
Antiguo 01-07-2006
Avatar de dec
dec dec is offline
Moderador
 
Registrado: dic 2004
Ubicación: Alcobendas, Madrid, España
Posts: 13.107
Poder: 34
dec Tiene un aura espectaculardec Tiene un aura espectacular
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 código anterior tiene un error, y un mal aprovechamiento de recursos:

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();
Pero no hubiera podido resolver la duplicación de código. Mi colega Dave viene utilizando desde hace tiempo la cláusula __finally de las excepciones estructuradas de C (no C++) y le ha funcionado de maravillas, sin problema alguno:

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
  }
}
Lo que me preocupa es que éste no es el estilo empleado por la propia Inprise en sus manuales, pero repito: funciona sin problema alguno.

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; }
};
Esta plantilla define dos constructores: en uno se le pasa un puntero a un objeto arbitrario recién creado, y el otro no recibe nada, pero crea internamente un objeto. La clase con la que se instancia la plantilla debe permitir en este caso el uso de constructores con un solo parámetro: este el caso de cualquier componente de la VCL, que necesitan un owner o propietario. A este constructor se le pasa el puntero NULL, pues si se trata de un objeto de vida limitada, el propietario no tiene importancia alguna. En cualquier caso, el destructor destruye la instancia asignada durante la construcción. Recuerde que cuando definimos una variable de clase local a una rutina en C++, este lenguaje garantiza siempre su destrucción, aunque se produzcan excepciones. Es como si tuviéramos una instrucción try/finally implícita.

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;
}
En ambos casos, el objeto creado se destruye inexorablemente al finalizar el método, aunque se produzca una excepción durante su ejecución, pues de ello se encarga el compilador. Si intentásemos utilizar el método correcto en C++ Builder que no utiliza smart pointers ni __finally, esto sería lo que obtendríamos:

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;
}
De todos modos, me gustaría escuchar opiniones diferentes a la mía.

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;
}
De todos modos, la diatriba anterior sigue estando justificada. Los ejemplos de programas con la VCL en C++ Builder de Inprise, y de la mayoría de los libros que andan por ahí no sólo carecen de la más mínima elegancia, sino que muchos de ellos son además incorrectos.
Responder Con Cita
Respuesta


Herramientas Buscar en Tema
Buscar en Tema:

Búsqueda Avanzada
Desplegado

Normas de Publicación
no Puedes crear nuevos temas
no Puedes responder a temas
no Puedes adjuntar archivos
no Puedes editar tus mensajes

El código vB está habilitado
Las caritas están habilitado
Código [IMG] está habilitado
Código HTML está deshabilitado
Saltar a Foro


La franja horaria es GMT +2. Ahora son las 12:35:47.


Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi
Copyright 1996-2007 Club Delphi