elhacker.net cabecera Bienvenido(a), Visitante. Por favor Ingresar o Registrarse
¿Perdiste tu email de activación?.

 

 


Tema destacado: Trabajando con las ramas de git (tercera parte)


  Mostrar Mensajes
Páginas: 1 ... 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 [25] 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 ... 125
241  Programación / Programación C/C++ / Re: cifrar un entero de 4 cifras en lenguaje C. en: 9 Septiembre 2014, 23:17 pm
Lo suyo sería realizar el procedimiento en dos pasos:

* calcular el residuo
* reubicar los dígitos

La primera opción se puede resolver con un bucle, la segunda hay que hacerla más a mano:

Código
  1. int CifrarNumero( int numero )
  2. {
  3.  int digitos[4];
  4.  
  5.  // Paso 1 : transformación
  6.  int i;
  7.  for ( i = 0; i < 4; i++ )
  8.  {
  9.    digitos[ 3 - i ] = ( ( numero % 10 ) + 7 ) % 10;
  10.    numero /= 10;
  11.  }
  12.  
  13.  // Paso 2 : permutación
  14.  digitos[ 0 ] ^= digitos[ 2 ]
  15.  digitos[ 2 ] ^= digitos[ 0 ];
  16.  digitos[ 0 ] ^= digitos[ 2 ];
  17.  
  18.  digitos[ 1 ] ^= digitos[ 3 ];
  19.  digitos[ 3 ] ^= digitos[ 1 ];
  20.  digitos[ 1 ] ^= digitos[ 3 ];
  21.  
  22.  // Paso 3: Recomponemos el número.
  23.  int resultado = 0;
  24.  for ( i=0; i < 4; i++ )
  25.  {
  26.    resultado *= 10;
  27.    resultado += digitos[ i ];
  28.  }
  29.  
  30.  return resultado;
  31. }

Se puede optimizar, pero yo creo que funciona... no tengo compilador en el teléfono
242  Programación / Programación C/C++ / [APORTE] C++ y bases de datos en: 9 Septiembre 2014, 16:42 pm
Introducción

En este documento intento plasmar un poco mi experiencia a la hora de tratar con bases de datos. Se que está muy de moda usar herramientas que automatizan la conexión y hacen gran parte del trabajo por nosotros. Sin embargo ésta es una de las cosas que prefiero hacer por mi cuenta, ya que he visto fracasar bastantes proyectos debido a una mala gestión de la base de datos.

Ojo, no me considero un experto sobre el tema, de hecho, mi preparación es ingeniero de telecomunicaciones y en la carrera he visto tanta teoría sobre bases de datos como la que pueden dar unos niños en la guardería.

Se aceptan críticas constructivas, comentarios, anotaciones, mejoras... en fín, lo de siempre.

Como también se pone de moda esto de las licencias, este código se presenta tal cual, sin garantía de ningún tipo. Sois libres de copiar y/o modificar el código a vuestro antojo, sin embargo declino toda responsabilidad que su mal uso pueda conllevar. También sois libres de coger el código y guardarlo en algún rincón oscuro como vuestro tesoro o usarlo para hechizos y brujería, eso si, siempre bajo vuestra responsabilidad.

Nota adicional: No puedo garantizar que el código esté 100% libre de errores, ya que la mayor parte de los ejemplos no son ejecutables y los he tenido que ir retocando (con cuidado, eso sí) para adaptarlos a la guía. Aún así, me parece importante recalcar que el código está escrito bajo el estándar C++11, así que cuidado con usar compiladores no compatibles.

Bueno, dicha la parte aburrida vamos al tema. Antes o después, todo el que se dedica al mundo de la programación, ya sea por hobby o profesionalmente, acaba haciendo uso de bases de datos. Esto suele ser debido a que tenemos la mala costumbre de querer guardar todo tipo de información... con lo divertido que es reescribir los documentos una y otra vez.

Debido a que no suele ser demasiado eficiente eso de resetear la base de datos y rellenarla de nuevo cada vez que queremos almacenar algún cambio, lo lógico es que nos veamos obligados a guardar determinada información sobre los objetos que se encuentran en la base de datos:

  • Necesitamos saber si el objeto es nuevo o no (no es lo mismo CREATE que UPDATE).
  • Necesitamos saber si el objeto ha sido modificado. Si no lo hacemos tendremos que actualizar TODOS, con el tiempo que ello pueda conllevar.
  • En el caso de tablas con campo autoincremental, necesitamos almacenar dicho OID para futuras consultas.

La primera aproximación que yo creo que hemos hecho todos es meter toda esta información a piñón en el propio objeto. Algo del tipo:

Código
  1. class User
  2. {
  3.  public:
  4.  
  5.    // Interfaz para la base de datos
  6.  
  7.    // Devuelve el OID ( en el caso de objetos nuevos vale 0 )
  8.    int OID( ) const;
  9.  
  10.    // Permite modificar el OID
  11.    void SetOID( int oid );
  12.  
  13.    // Indica si el objeto es nuevo (no esta aun en la BD )
  14.    bool IsNew( ) const;
  15.  
  16.    // Indica si el objeto ha sido modificado
  17.    bool IsModified( ) const;
  18.  
  19.    // Reinicia los flags 'IsNew' e 'IsModified'
  20.    void ObjectSaved( );
  21.  
  22.    // Interfaz propia del objeto
  23.  
  24.    std::string Name( ) const;
  25.  
  26.    std::string Surname( ) const;
  27.  
  28.    // ...
  29. };

Como queda patente de un primer vistazo, esta solución no parece demasiado elegante. Estamos introduciendo acoplamiento al introducir en la interfaz elementos propios de la gestión de la base de datos. Además, este diseño plantea otros problemas:

  • Si se usan plugins, exponemos información sensible de la base de datos a terceros.
  • Si nuestra aplicación trabaja en red nos obliga a enviar información innecesaria en el lado del cliente.

El diseño, al fin y al cabo, es feo, ya puestos, también podemos poner en 'User' los métodos para leer y escribir en la base de datos, un par de métodos también para poder almacenar la información en un fichero y, si nos quedan más ganas de marcha, también podemos meter en la clase el código que gestione la interfaz gráfica...(espero que se entienda la ironía).

Después de un rato pensando, a alguien podría decir... "bueno, creo una clase base que tenga los datos de la BD y soluciono el problema con herencia:

Código
  1. class DBItem
  2. {
  3.  public:
  4.  
  5.    // Interfaz para la base de datos
  6.  
  7.    // Devuelve el OID ( en el caso de objetos nuevos vale 0 )
  8.    int OID( ) const;
  9.  
  10.    // Permite modificar el OID
  11.    void SetOID( int oid );
  12.  
  13.    // Indica si el objeto es nuevo (no esta aun en la BD )
  14.    bool IsNew( ) const;
  15.  
  16.    // Indica si el objeto ha sido modificado
  17.    bool IsModified( ) const;
  18.  
  19.    // Reinicia los flags 'IsNew' e 'IsModified'
  20.    void ObjectSaved( );
  21. };
  22.  
  23. class User : public DBItem
  24. {
  25.  public:
  26.  
  27.    // Interfaz propia del objeto
  28.  
  29.    std::string Name( ) const;
  30.  
  31.    std::string Surname( ) const;
  32.  
  33.    // ...
  34. };

No voy a negarlo, es una posible solución, aunque este diseño sigue teniendo los mismos problemas de acoplamiento que el diseño anterior. La ventaja, eso sí, es que podremos reutilizar código de 'DBItem', no iban a ser todo malas noticias. Sin embargo, como comentaba, este diseño sigue sin convencer.

La idea entonces parece que pasa por separar físicamente la información relativa a la base de datos de los datos propios del objeto. El problema que aparece entonces es cómo conseguir esto de la forma más limpia posible, intentando además que sea reutilizable. Los requisitos que ha de cumplir este sistema son los siguientes:

  • Debe ser posible separar la lógica de negocio de la capa de acceso a datos.
  • Debe permitir la gestión de elementos eliminados.
  • Debe ser capaz de detectar elementos nuevos y elementos modificados.
  • La solución debe ser reutilizable.



Separar la lógica de negocio de la capa de acceso a datos.

Una posible solución, la que trataré en este tema, pasa por crear un contenedor a modo de caché.

El contenedor va a estar dividido en 2 niveles:

  • Nivel 1: El contenedor. Representa la interfaz accesible por la aplicación. Debe permitir realizar las operaciones habituales sobre los objetos que gestiona: creación, borrado, edición, etc.
  • Nivel 2: Información de base de datos. El contenedor almacenará instancias de un objeto que almacenará la información relevante de la base de datos. Este objeto únicamente será accesible por el contenedor.

De acuerdo con lo expuesto, la implementación de la clase interna del contenedor debería tener un aspecto similar al siguiente:

Código
  1. /*
  2.  * Gestiona información sobre un elemento almacenado en una caché.
  3.  * Los objetos se guardan indizados por clave única.
  4.  */
  5. class UserCacheItem final
  6. {
  7.    UserCacheItem( ) = delete;
  8.  
  9.    UserCacheItem(
  10.              const UserCacheItem& ar_other ) = delete;
  11.  
  12.    UserCacheItem& operator=(
  13.              const UserCacheItem& ar_other ) = delete;
  14.  
  15.  public:
  16.  
  17.    /*
  18.      * Constructor de la clase.
  19.      */
  20.    UserCacheItem(
  21.              User* ap_item,
  22.              int a_key )
  23.      : _item{ ap_item },
  24.        _key{ a_key }
  25.    {
  26.    }
  27.  
  28.    /*!
  29.      * Destructor de la clase.
  30.      */
  31.    virtual ~UserCacheItem( )
  32.    {
  33.    }
  34.  
  35.    /*!
  36.      * Método llamado por la caché cuando se han almacando los cambios en la base de datos.
  37.      */
  38.    void Commit( )
  39.    {
  40.      if ( _tempOid )
  41.      {
  42.        _key = *_tempOid;
  43.        _tempOid.reset( );
  44.      }
  45.    }
  46.  
  47.    /*!
  48.      * Indica si el objeto gestionado es nuevo o no.
  49.      */
  50.    inline bool IsNew( ) const
  51.    {
  52.      return 0 == _key;
  53.    }
  54.  
  55.    /*!
  56.      * Indica si el objeto gestionado ha sufrido cambios desde el último commit.
  57.      */
  58.    inline bool IsModified( ) const
  59.    {
  60.      return !IsNew( ) && !_tempOid && _hash != _item->Hash( );
  61.    }
  62.  
  63.    /*!
  64.      * Expone el puntero al objeto gestionado por la instancia.
  65.      */
  66.    inline User* Item( ) const;
  67.    {
  68.      return _item.get( );
  69.    }
  70.  
  71.    /*!
  72.      * Recupera la clave única utilizada para identificar el objeto gestionado.
  73.      */
  74.    int Key( ) const
  75.    {
  76.      if ( _tempOid )
  77.        return *_tempOid;
  78.      else
  79.        return _key;
  80.    }
  81.  
  82.    /*!
  83.      * Recupera la clave original.
  84.      */
  85.    inline int OriginalKey( ) const
  86.    {
  87.      return _key;
  88.    }
  89.  
  90.    /*!
  91.      * Permite modificar la clave del objeto.
  92.      */
  93.    void ChangeKey(
  94.              int a_key )
  95.    {
  96.      if ( ar_key == _key )
  97.      {
  98.        _tempOid.reset( );
  99.      }
  100.      else if ( ( !_tempOid ) || ( a_key != *_tempOid ) )
  101.      {
  102.        _tempOid.reset( new int{ a_key } );
  103.      }
  104.    }
  105.  
  106.  private:
  107.  
  108.    std::unique_ptr< User* > _item;
  109.    int _key;
  110.    std::unique_ptr< int > _tempKey
  111. };

Como se ve, internamente se usan punteros inteligentes. Desde mi punto de vista el uso de punteros inteligentes tiene dos ventajas básicas:

  • Evitar lagunas de memoria.
  • Queda claro que esta clase se encarga de controlar el ciclo de vida de los objetos de tipo 'User', lo cual evita malentendidos con los 'delete'.
  • Facilita el mantenimiento del código.

Además, ya se cómo funciona la memoria dinámica, no necesito practicarlo cada vez que tenga que crear objetos... y más cuando su ciclo de vida está tan delimitado. Prefiero centrar mis esfuerzos en problemas más serios.

En cuanto al control del oid, se puede apreciar que se usan dos claves: _key y _tempKey. Para almacenar el oid se ha optado por un mecanismo en dos pasos:

  • El valor inicial de _key se establece al crear el objeto.
  • Al llamar a ChangeKey se modifica _tempKey.
  • Al llamar a 'Commit' el valor de _key se modifica por _tempKey, suponiendo que tenga valor.

Este mecanismo en dos pasos permite "recordar" la clave que tenía inicialmente el objeto de cara a poder localizarlo en la base de datos en base a dicha clave. Recordemos que las actualizaciones a la base de datos suelen ser del tipo: "UPDATE users SET key=30 WHERE key=20". Si no guardamos ese '20', dificilmente podremos localizar el registro en la base de datos.

Entiendo que en este caso cambiar el oid puede no tener demasiado sentido, pero no siempre es así.

Hablando ahora del contenedor, su interfaz se detalla a continuacion:

Código
  1. /*!
  2.  * Contenedor de BD para objetos de tipo "User"
  3.  */
  4. class UserCache
  5. {
  6.    UserCache(
  7.              const UserCache& ar_other ) = delete;
  8.  
  9.    UserCache& operator=(
  10.              const UserCache& ar_other ) = delete;
  11.  
  12.  public:
  13.  
  14.    /*!
  15.      * Constructor por defecto.
  16.      */
  17.    UserCache( )
  18.    {
  19.    }
  20.  
  21.    /*!
  22.      * Destructor.
  23.      */
  24.    virtual ~UserCache( )
  25.    {
  26.    }
  27.  
  28.    /*!
  29.      * Inserta un nuevo elemento en la cache
  30.      */
  31.    bool Add(
  32.              User* ap_element,
  33.              int a_key )
  34.    {
  35.      if ( ap_element )
  36.      {
  37.        auto it = _items.find( a_key );
  38.        if ( it == _items.end( ) )
  39.        {
  40.          UserCacheItem* newItem = new UserCacheItem{ ap_element, a_key };
  41.          _items.insert( std::make_pair( a_key, std::unique_ptr< UserCacheItem >{ newItem } ) );
  42.          return true;
  43.        }
  44.      }
  45.  
  46.      return false;
  47.    }
  48.  
  49.    /*!
  50.      * Obtiene la clave para un usuario.
  51.      */
  52.    int Key(
  53.              const User* ap_element ) const
  54.    {
  55.      auto it = FindItem( *ap_element, _items );
  56.      if ( it != _items.end( ) )
  57.        return it->first;
  58.  
  59.      return 0;
  60.    }
  61.  
  62.    /*!
  63.      * Recupera la lista de usuarios.
  64.      */
  65.    std::vector< User* > Items( ) const
  66.    {
  67.      std::vector< User* > to_return;
  68.  
  69.      for ( auto it = _items.begin( ); it != _items.end( ); ++it )
  70.        to_return.push_back( it->second->Item( ) );
  71.  
  72.      return to_return;
  73.    }
  74.  
  75.    std::map< int, User* > MappedItems( ) const
  76.    {
  77.      std::map< int, User* > to_return;
  78.  
  79.      for ( auto it = _items.begin( ); it != _items.end( ); ++it )
  80.        to_return.insert( std::make_pair( it->first, it->second->Item( ) ) );
  81.  
  82.      return to_return;
  83.    }
  84.  
  85.    /*!
  86.      * Recupera un usuario en base a su oid.
  87.      */
  88.    User* Item(
  89.              int a_key ) const
  90.    {
  91.      auto it = _items.find( a_key );
  92.      if ( it != _items.end( ) )
  93.        return it->second->Item( );
  94.  
  95.      return nullptr;
  96.    }
  97.  
  98.    /*!
  99.      * Permite cambiar el oid de un usuario.
  100.      */
  101.    bool ChangeKey(
  102.              const User* ap_element,
  103.              int a_key );
  104.    {
  105.      if ( ap_element )
  106.      {
  107.        auto it = _items.find( a_key );
  108.        if ( it != _items.end( ) )
  109.          return ( it->second->Item( ) == ap_element );
  110.  
  111.        it = FindItem( *ap_element, _items );
  112.        if ( it != _items.end( ) )
  113.        {
  114.          // Si cambia la clave tenemos que actualizar el mapa de elementos
  115.          it->second->SetKey( ar_key );
  116.          _items.insert( std::make_pair( ar_key, std::move( it->second ) ) );
  117.          _items.erase( it );
  118.          return true;
  119.        }
  120.      }
  121.  
  122.      return false;
  123.    }
  124.  
  125.    /*!
  126.      * Marca todos los usuarios como "no modificados", además se encarga de
  127.      * descartar todos los elementos eliminados.
  128.      */
  129.    void Commit( )
  130.    {
  131.      for ( auto& item : _items )
  132.        item.second->Commit( );
  133.    }
  134.  
  135.    /*!
  136.      * Reinicia la caché. Elimina todo su contenido.
  137.      */
  138.    void Clear( )
  139.    {
  140.      _items.clear( );
  141.    }
  142.  
  143.    /*!
  144.      * Indica si hay cambios pendientes de llevar a la base de datos.
  145.      */
  146.    bool ChangesPending( ) const;
  147.    {
  148.      for ( auto& item : _items )
  149.      {
  150.        if ( item.second->IsModified( ) )
  151.          return true;
  152.      }
  153.  
  154.      return false;
  155.    }
  156.  
  157.  private:
  158.  
  159.    std::map< int, std::unique_ptr< UserCacheItem > > _items;
  160.  
  161.    std::map< int, User* >::iterator FindItem(
  162.              const User& ar_element ) const
  163.    {
  164.      return std::find_if( _items.begin( ),
  165.                           _items.end( ),
  166.                           [&ar_element]( const std::pair< int, User* >& pair )
  167.                           { return pair.second->Item( ) == &ar_element; } );
  168.    }
  169.  
  170. };

Bueno, con este diseño parece que hemos conseguido desacoplar el uso de la base de datos del objeto 'User'. No está mal para empezar. Sin embargo aún queda camino por recorrer.



Borrar usuarios

Normalmente, cuando en una aplicación se decide eliminar información, éste borrado no suele ser inmediato. En ocasiones hay que esperar a que el usuario confirme la operación dando la orden de "guardar cambios", mientras que en otras ocasiones interesa retrasar las eliminaciones con la intención de intentar agruparlas para optimizar las consultas a la base de datos.

Para satisfacer esta necesidad, nuestro contenedor va a encargarse de la gestión de los elementos borrados de la siguiente forma:

  • Cuando le indicamos que deseamos borrar un elemento que ya se encuentra gestionado por el contenedor lo introduce en un listado de elementos a eliminar. Los elementos no se eliminarán de forma efectiva hasta que no hagamos "commit".
  • Cuando le indicamos que deseamos borrar un elemento que NO se encuentra gestionado por el contenedor lo elimina directamente. Esto es así porque al no estar gestionado por el contenedor no es posible que otras partes del código conozcan este objeto.

Un ejemplo del código que permite cumplir con esta función lo encontramos a continuación:

Código
  1. class UserCache
  2. {
  3.  public:
  4.  
  5.    bool Delete(
  6.        int a_key )
  7.    {
  8.      auto it = _items.find( a_key );
  9.      if ( it != _items.end( ) )
  10.      {
  11.        _deletedItems.push_back( std::move( it->second ) );
  12.        _items.erase( it );
  13.        return true;
  14.      }
  15.  
  16.      return false;
  17.    }
  18.  
  19.    bool Delete(
  20.        User* ap_element )
  21.    {
  22.      if ( ap_element )
  23.      {
  24.        auto it = FindItem( *ap_element );
  25.  
  26.        if ( it != _items.end( ) )
  27.        {
  28.          _deletedItems.push_back( std::move( it->second ) );
  29.          _items.erase( it );
  30.          return true;
  31.        }
  32.      }
  33.  
  34.      return false;
  35.    }
  36.  
  37.    std::map< int, User* > ToDelete( ) const
  38.    {
  39.      std::map< int, User* > to_return;
  40.  
  41.      for ( auto& pair : _deletedItems )
  42.        to_return.insert( std::make_pair( pair.first, pair.second->Item( ) ) );
  43.  
  44.      return to_return;
  45.    }
  46.  
  47.    void Commit( )
  48.    {
  49.      _deletedItems.clear( );
  50.  
  51.      for ( auto& item : _items )
  52.        item.second->Commit( );
  53.    }
  54.  
  55.    void Clear( )
  56.    {
  57.      _items.clear( );
  58.      _deletedItems.clear( );
  59.    }
  60.  
  61.    bool ChangesPending( ) const
  62.    {
  63.      if ( !_deletedItems.empty( ) )
  64.        return true;
  65.  
  66.      for ( auto& item : _items )
  67.      {
  68.        if ( item.second->IsModified( ) )
  69.          return true;
  70.      }
  71.  
  72.      return false;
  73.   }
  74.  
  75.  private:
  76.  
  77.    std::vector< std::unique_ptr< User > > _deletedItems;
  78. };

Como se puede apreciar, el contenedor nos va a ofrecer en un cómodo listado todos los objetos que se han marcado como borrados. Si recogemos este listado podemos



Modificar usuarios

Cuando recurrimos al método 'feo' para interaccionar con bases de datos, a menudo caemos en la tentación de registrar las modificaciones con un código similar al siguiente:

Código
  1. class User
  2. {
  3.  public:
  4.  
  5.    User( )
  6.      : _modified{ false }
  7.    { }
  8.  
  9.    std::string Name( ) const
  10.    { return _name; }
  11.  
  12.    void SetName( const std::string& name )
  13.    {
  14.      if ( name != _name )
  15.      {
  16.        _name = name;
  17.        _modified = true;
  18.      }
  19.    }
  20.  
  21.    bool IsModified( ) const
  22.    { return _modified; }
  23.  
  24.  private:
  25.  
  26.    std::string _name;
  27.    bool _modified;
  28. };

Esta solución ya no nos sirve, ya que intentamos que la clase 'User' sera totalmente independiente de la base de datos.

Para poder marcar los objetos como modificados se puede optar por varias soluciones diferentes, os enumero tres de ellas:

  • Hacer una llamada del tipo 'UserCache::SetModified( usuario )' cada vez que modifiquemos un objeto. Este mecanismo tiene el inconveniente de ser complicado de gestionar, ya que obliga a meter esta instrucción en todas las funciones que modifiquen el objeto. Si el objeto se modifica en zonas muy concretas de la aplicación puede ser una solución bastante factible. PD.: no es recomendado si la clase puede ser modificada por plugins.
Código
  1. class UserCache
  2. {
  3.  public:
  4.    void SetModified( User* ap_element );
  5. };
  6.  
  7. class Something
  8. {
  9.  public:
  10.  
  11.    void UpdateUser( User* user );
  12.    {
  13.      user->SetName( anotherName );
  14.      user->SetSurname( anotherSurname );
  15.  
  16.      // El método realmente no es estático, lo pongo asi por legibilidad
  17.      UserCache::SetModified( ap_element );      
  18.    }
  19. };

  • Utilizar un hash para identificar los elementos que han cambiado. Este hash debe estar mapeado en el contenedor para poder detectar cambios. Este mecanismo puede implementarse en el contenedor o en la clase 'User'. Esta solución, aparte de ser más limpia ofrece código reutilizable, ya que el hash puede usarse, por ejemplo, en los operadores de igualdad.
Código
  1. class User
  2. {
  3.  public:
  4.    std::size_t hash( ) const
  5.    {
  6.      const std::size_t name{ std::hash< std::string >( )( _name ) };
  7.      const std::size_t surname { std::hash< std::string >( )( _surname ) };
  8.      return name ^ ( surname << 1 );
  9.    }
  10. };

  • Utilizar un "contador de versión". El contador de versión puede ser tan sencillo como un "unsigned int" que se va incrementando cada vez que se realiza un cambio en el objeto. Entonces, basta con que el contenedor realice un mapeo de la versión de cada objeto para poder identificar los elementos modificados. La ventaja de este sistema respecto al hash es que es más rápido, la desventaja es que requiere actualizar los setters. Este método se parece bastante al "método feo" que iniciaba este apartado, es cierto.
Código
  1. class User
  2. {
  3.  public:
  4.  
  5.    User( )
  6.      : _version{ 0 }
  7.    { }
  8.  
  9.    void SetName( const std::string& name )
  10.    {
  11.      if ( name != _name )
  12.      {
  13.        _name = name;
  14.        ++_version;
  15.      }
  16.    }
  17.  
  18.    unsigned int Version( ) const
  19.    { return _version; }
  20. };

Una vez que ya hemos elegido el mecanismo que vamos a implementar tenemos que adaptar el funcionamiento del contenedor para que sea capaz de identificar los elementos que han sufrido cambios. En este caso, el código de ejemplo presupone que se ha tomado como buena la opción de hash. Adaptar el código para que funcione con la opción del "contador de versiones" es bastante sencillo:

Clase UserCacheItem:
Código
  1. class UserCacheItem final
  2. {
  3.  public:
  4.  
  5.    /*!
  6.      * \brief Constructor de la clase.
  7.      */
  8.    UserCacheItem(
  9.        User* ap_item,
  10.        int a_key )
  11.      : _item{ ap_item },
  12.        _key{ ar_key },
  13.        _hash{ ap_item->Hash( ) }
  14.    {
  15.    }
  16.  
  17.    /*
  18.      * Método llamado por la caché cuando se han almacando los cambios en la base de datos.
  19.      */
  20.    void Commit( )
  21.    {
  22.      if ( _tempOid )
  23.      {
  24.        _key = *_tempOid;
  25.        _tempOid.reset( );
  26.      }
  27.  
  28.      _hash = ap_item->Hash( );
  29.    }
  30.  
  31.    /*!
  32.      * Indica si el objeto gestionado ha sufrido cambios desde el último commit.
  33.      */
  34.    inline bool IsModified( ) const
  35.    { return !IsNew( ) && !_tempOid && _hash != _item->Hash( ); }
  36.  
  37.  private:
  38.  
  39.    std::unique_ptr< User > _item;
  40.    int _key;
  41.    std::unique_ptr< int > _tempOid;
  42.    std::size_t _hash;
  43.  
  44. };

Clase UserCache:
Código
  1. class UserCache
  2. {
  3.  public:
  4.  
  5.    std::set< User* > ToCreate( ) const
  6.    {
  7.      std::set< User* > to_return;
  8.  
  9.      for ( auto& item : _items )
  10.      {
  11.        if ( item.second->IsNew( ) )
  12.          to_return.insert( item.second->Item( ) );
  13.      }
  14.  
  15.      return to_return;
  16.    }
  17.  
  18.    std::map< int, User* > ToUpdate( ) const
  19.    {
  20.      std::map< int, User* > to_return;
  21.  
  22.      for ( auto& item : _items )
  23.      {
  24.        if ( item.second->IsModified( ) )
  25.          to_return.insert( std::make_pair( item.first, item.second->Item( ) ) );
  26.      }
  27.  
  28.      return to_return;
  29.    }
  30.  
  31.    bool ChangesPending( ) const
  32.    {
  33.      for ( auto& item : _items )
  34.      {
  35.        if ( item.second->IsModified( ) )
  36.          return true;
  37.      }
  38.  
  39.      return false;
  40.    }
  41. };



Conectar el contenedor con la base de datos

Una de las primeras motivaciones que nos ha llevado a este punto ha sido la necesidad de reducir el nivel de acoplamiento en nuestras clases. Por este motivo vamos a delegar la responsabilidad de la comunicación con la base de datos en una nueva clase.

A mí se me ha ocurrido llamarla 'DBUsers', para gustos, los nombres de las clases. Bueno, al tema, el caso es que esta clase debería proporcionar métodos para leer y escribir en las cachés de datos. Personalmente, en esta clase no me preocupa demasiado que haya acoplamiento ya que, como norma general, cada aplicación tiene su propio diseño de base de datos, por lo que el código de esta clase dificilmente será reutilizable.

Código
  1. class DBUsers
  2. {
  3.    DBUsers( ) = delete;
  4.  
  5.    DBUsers(
  6.          const DBUsers& ar_other ) = delete;
  7.  
  8.    const DBUsers& operator=(
  9.          const DBUsers& ar_other ) = delete;
  10.  
  11.  public:
  12.  
  13.    DBUsers(
  14.           const UniqueKeyCache< User, int >& ar_cache )
  15.      : _cache{ &ar_cache }
  16.    {
  17.    }
  18.  
  19.    virtual ~DBUsers( )
  20.    {
  21.    }
  22.  
  23.    bool SaveData( )
  24.    {
  25.      bool ok = true;
  26.  
  27.      // Este primer chequeo no sería necesario, pero me gusta la simetría
  28.      // y además así aseguro que se elimina deletedItems
  29.      if ( ok )
  30.      {
  31.        // Primero es recomendable realizar la operación de borrado
  32.        auto deletedItems = _cache.ItemsToDelete( );
  33.        for ( auto& pair : deletedItems )
  34.        {
  35.          // Generación y ejecución de las consultas de eliminación.
  36.          // Si algúna consulta da error, actualizamos ok a false
  37.        }
  38.      }
  39.  
  40.      if ( ok )
  41.      {
  42.        // Después actualizamos los elementos modificados
  43.        auto updatedItems = _cache.ItemsToUpdate( );
  44.        for ( auto& pair : updatedItems )
  45.        {
  46.          // Generación y ejecución de las consultas de actualización
  47.          // Si algúna consulta da error, actualizamos ok a false
  48.        }
  49.      }
  50.  
  51.      // Finalmente damos de alta los nuevos registros
  52.      if ( ok )
  53.      {
  54.        auto newItems = _cache.ItemsToCreate( );
  55.        for ( auto& item : newItems )
  56.        {
  57.          // Generación y ejecución de las consultas de creación
  58.          // Si algúna consulta da error, actualizamos ok a false
  59.  
  60.          // No hay que olvidarse de recuperar el OID de cada elemento
  61.          // y actualizar la cache.
  62.          _cache->ChangeKey( &item, newOid );
  63.        }
  64.      }
  65.  
  66.      if ( ok )
  67.        _cache->Commit( );
  68.      else
  69.      {
  70.        // Si se produce algún error tenemos que decidir entre resetear la cache
  71.        // o permitir al usuario intentarlo más veces.
  72.      }
  73.    }
  74.  
  75.    void LoadUsers( )
  76.    {
  77.      // Consulta de selección sobre la tabla de usuarios
  78.      // ...
  79.      while ( row.next( ) ) // Para cada registro leído...
  80.      {
  81.        User* user = new User{ };
  82.        user->SetName( row.data( "name" ).toString( ) );
  83.        user->SetSurname( row.data( "surname" ).toString( ) );
  84.  
  85.        _cache->Add( user, row.data( "id" ).toInt( ) );
  86.      }
  87.    }
  88.  
  89.    // No hace falta leer toda la tabla... se puede leer bajo demanda para
  90.    // reducir los tiempos de acceso.
  91.    void LoadUsers( const std::string& name );
  92.  
  93.  private:
  94.  
  95.    UniqueKeyCache< User, int >* _cache;
  96.  
  97. };

Como se ve, la caché la almaceno por referencia. Si no tengo necesidad de eliminar un objeto en un contexto dado, y además necesito que ese objeto exista, prefiero usar una referencia que un puntero, así queda claro que no es competencia de ese contexto hacer un delete. Además, la idea de que la caché pueda utilizarse de forma independiente permite aislar la capa de acceso a base de datos, piensa que no es obligatorio que los datos también se pueden guardar en un fichero o enviar por red... este diseño permite tener una clase específica para cada uso y sin necesidad de que se conozcan entre ellas.



Reutilización del código

El código ya nos funciona para la clase 'User', pero la gracia es que todo este esfuerzo pueda ser reutilizado con otras clases. La mejor forma para hacer esto es convertir la caché en un template... el código puede que se ensucie un poco con esta conversión, pero creo que es un sacrificio razonable que se va a compensar con creces:

Template CacheItem (sustituye a UserCacheItem):
Código
  1. /*!
  2.  * Gestiona información sobre un elemento almacenado en una caché.
  3.  * Los objetos se guardan indizados por clave única.
  4.  */
  5. template< class _CLASS_, class _KEY_ >
  6. class CacheItem final
  7. {
  8.    CacheItem( ) = delete;
  9.  
  10.    CacheItem(
  11.        const CacheItem& ar_other ) = delete;
  12.  
  13.    CacheItem& operator=(
  14.        const CacheItem& ar_other ) = delete;
  15.  
  16.  public:
  17.  
  18.    /*!
  19.      * Constructor de la clase.
  20.      */
  21.    CacheItem(
  22.        _CLASS_* ap_item,
  23.        const _KEY_& ar_key );
  24.  
  25.    /*!
  26.      * Destructor de la clase.
  27.      */
  28.    virtual ~CacheItem( );
  29.  
  30.    /*!
  31.      * Método llamado por la caché cuando se han almacando los cambios en la base de datos.
  32.      */
  33.    void Commit( );
  34.  
  35.    /*!
  36.      * Indica si el objeto gestionado es nuevo o no.
  37.      */
  38.    inline bool IsNew( ) const
  39.    { return _KEY_{ } == _key; }
  40.  
  41.    /*!
  42.      * Indica si el objeto gestionado ha sufrido cambios desde el último commit.
  43.      */
  44.    inline bool IsModified( ) const
  45.    { return !IsNew( ) && !_tempOid && _hash != _item->Hash( ); }
  46.  
  47.    /*!
  48.      * Expone el puntero al objeto gestionado por la instancia.
  49.      */
  50.    inline _CLASS_* Item( ) const
  51.    { return _item.get( ); }
  52.  
  53.    /*!
  54.      * Recupera la clave única utilizada para identificar el objeto gestionado.
  55.      */
  56.    _KEY_ Key( ) const;
  57.  
  58.    /*!
  59.      * Recupera la clave original.
  60.      */
  61.    inline _KEY_ OriginalKey( ) const
  62.    { return _key; }
  63.  
  64.    /*!
  65.      * Permite modificar la clave del objeto.
  66.      */
  67.    void ChangeKey(
  68.        const _KEY_& ar_key )
  69.    {
  70.      if ( ar_key == _key )
  71.      {
  72.        _tempOid.reset( );
  73.      }
  74.      else if ( ( !_tempOid ) || ( ar_key != *_tempOid ) )
  75.      {
  76.        _tempOid.reset( new _KEY_{ ar_key } );
  77.      }
  78.    }
  79.  
  80.  private:
  81.  
  82.    std::unique_ptr< _CLASS_ > _item;
  83.    _KEY_ _key;
  84.    std::unique_ptr< _KEY_ > _tempOid;
  85.    std::size_t _hash;
  86.  
  87. };
  88.  
  89. template< class _CLASS_, class _KEY_ >
  90. CacheItem< _CLASS_,_KEY_ >::CacheItem(
  91.          _CLASS_* ap_item,
  92.          const _KEY_& ar_key )
  93.  : _item{ ap_item },
  94.    _key{ ar_key },
  95.    _hash{ ap_item->Hash( ) }
  96. {
  97. }
  98.  
  99. template< class _CLASS_, class _KEY_ >
  100. CacheItem< _CLASS_,_KEY_ >::~CacheItem( )
  101. {
  102. }
  103.  
  104. template< class _CLASS_, class _KEY_ >
  105. void
  106. CacheItem< _CLASS_,_KEY_ >::Commit( )
  107. {
  108.  if ( _tempOid )
  109.  {
  110.    _key = *_tempOid;
  111.    _tempOid.reset( );
  112.  }
  113.  
  114.  _hash = _item->Hash( );
  115. }
  116.  
  117. template< class _CLASS_, class _KEY_ >
  118. _KEY_
  119. CacheItem< _CLASS_,_KEY_ >::Key( ) const
  120. {
  121.  if ( _tempOid )
  122.    return *_tempOid;
  123.  else
  124.    return _key;
  125. }

Se pueden apreciar algúnos cambios en lo relativo al tratamiento de la clave primaria. Dado que ahora esta clave puede ser de cualquier tipo (un string por ejemplo), los métodos ahora gestionan la clave por referencia.

Clase UniqueKeyCache (sustituye a UserCache):
Código
  1. template< class _CLASS_, class _KEY_ >
  2. class UniqueKeyCache
  3. {
  4.    UniqueKeyCache(
  5.        const UniqueKeyCache& ar_other ) = delete;
  6.  
  7.    UniqueKeyCache& operator=(
  8.        const UniqueKeyCache& ar_other ) = delete;
  9.  
  10.  public:
  11.  
  12.    /*!
  13.      * Constructor por defecto.
  14.      */
  15.    UniqueKeyCache( )
  16.    { }
  17.  
  18.    /*!
  19.      * Destructor.
  20.      */
  21.    virtual ~UniqueKeyCache( )
  22.    { }
  23.  
  24.    /*!
  25.      * Inserta un nuevo elemento en la cache.
  26.      */
  27.    bool Add(
  28.        _CLASS_* ap_element,
  29.        const _KEY_& ar_key );
  30.  
  31.    /*!
  32.      * Devuelve la clave asociada a un elemento.
  33.      */
  34.    _KEY_ Key(
  35.        const _CLASS_* ap_element ) const;
  36.  
  37.    /*!
  38.      * Marca un elemento para ser eliminado.
  39.      */
  40.    bool Delete(
  41.        const _KEY_& ar_key );
  42.  
  43.    /*!
  44.      * Marca un elemento para ser eliminado.
  45.      */
  46.    bool Delete(
  47.        _CLASS_* ap_element );
  48.  
  49.    /*!
  50.      * Devuelve el listado de elementos.
  51.      */
  52.    std::set< _CLASS_* > Items( ) const;
  53.  
  54.    /*!
  55.      * Devuelve el listado de elementos mapeados por la clave primaria.
  56.      */
  57.    std::map< _KEY_, _CLASS_* > MappedItems( ) const;
  58.  
  59.    /*!
  60.      * Devuelve un elemento dada su clave primaria.
  61.      */
  62.    _CLASS_* Item(
  63.        const _KEY_& ar_key ) const;
  64.  
  65.    /*!
  66.      * Devuelve la lista de elementos que aún no están dados de alta en la base
  67.      * de datos.
  68.      */
  69.    std::set< _CLASS_* > ToCreate( ) const;
  70.  
  71.    /*!
  72.      * Devuelve la lista de elementos a actualizar en la base de datos.
  73.      */
  74.    std::map< _KEY_, _CLASS_* > ToUpdate( ) const;
  75.  
  76.    /*!
  77.      * Devuelve la lista de elementos a eliminar de la base de datos.
  78.      */
  79.    std::map< _KEY_, _CLASS_* > ToDelete( ) const;
  80.  
  81.    /*!
  82.      * Permite modificar la clave primaria de un elemento.
  83.      */
  84.    bool ChangeKey(
  85.        const _CLASS_* ap_element,
  86.        const _KEY_& ar_key );
  87.  
  88.    /*!
  89.      * Limpia la lista de elementos nuevos y/o modificados y elimina los
  90.      * elementos marcados para borrar.
  91.      */    
  92.    void Commit( );
  93.  
  94.    /*!
  95.      * Limpia la caché, eliminando todo su contenido.
  96.      */
  97.    void Clear( );
  98.  
  99.    /*!
  100.      * Indica si hay o no cambios pendientes de llevar a la base de datos.
  101.      */
  102.    bool ChangesPending( ) const;
  103.  
  104.  private:
  105.  
  106.    using _Item_              = CacheItem< _CLASS_, _KEY_ >;
  107.    using _ItemMap_           = std::map< _KEY_, std::unique_ptr< _Item_ > >;
  108.    using _ItemPair_          = std::pair< _KEY_, std::unique_ptr< _Item_ > >;
  109.    using _ItemMapIterator_   = typename _ItemMap_::iterator;
  110.    using _ItemList_          = std::vector< std::unique_ptr< _Item_ > >;
  111.  
  112.    _ItemMap_ _items;
  113.    _ItemList_ _deletedItems;
  114.  
  115.    /*!
  116.      * Utilidad para buscar en la lista de elementos.
  117.      */
  118.    _ItemMapIterator_ FindItem(
  119.        _CLASS_* ap_element ) const
  120.    {
  121.      return std::find_if( _items.begin( ),
  122.                           _items.end( ),
  123.                           [ap_element]( const _ItemPair_& pair )
  124.                           { return pair.second->Item( ) == ap_element; } );
  125.    }
  126. };
  127.  
  128. template< class _CLASS_, class _KEY_ >
  129. bool
  130. UniqueKeyCache< _CLASS_, _KEY_ >::Add(
  131.          _CLASS_* ap_element,
  132.          const _KEY_& ar_key )
  133. {
  134.  if ( ap_element )
  135.  {
  136.    auto it = _items.find( ar_key );
  137.    if ( it == _items.end( ) )
  138.    {
  139.      _Item_* newItem = new _Item_{ ap_element, ar_key };
  140.      _items.insert( std::make_pair( ar_key, std::unique_ptr< _Item_ >( newItem ) ) );
  141.      return true;
  142.    }
  143.  }
  144.  
  145.  return false;
  146. }
  147.  
  148. template< class _CLASS_, class _KEY_ >
  149. _KEY_
  150. UniqueKeyCache< _CLASS_, _KEY_ >::Key(
  151.          const _CLASS_* ap_element ) const
  152. {
  153.  auto it = FindItem( *ap_element );
  154.  if ( it != _items.end( ) )
  155.    return it->first;
  156.  
  157.  return _KEY_{ };
  158. }
  159.  
  160. template< class _CLASS_, class _KEY_ >
  161. bool
  162. UniqueKeyCache< _CLASS_, _KEY_ >::Delete(
  163.          const _KEY_& ar_key )
  164. {
  165.  auto it = _items.find( ar_key );
  166.  if ( it != _items.end( ) )
  167.  {
  168.    _deletedItems.push_back( std::move( it->second ) );
  169.    _items.erase( it );
  170.    return true;
  171.  }
  172.  
  173.  return false;
  174. }
  175.  
  176. template< class _CLASS_, class _KEY_ >
  177. bool
  178. UniqueKeyCache< _CLASS_, _KEY_ >::Delete(
  179.          _CLASS_* ap_element )
  180. {
  181.  if ( ap_element )
  182.  {
  183.    auto it = FindItem( *ap_element );
  184.    if ( it != _items.end( ) )
  185.    {
  186.      _deletedItems.push_back( std::move( it->second ) );
  187.      _items.erase( it );
  188.      return true;
  189.    }
  190.  }
  191.  
  192.  return false;
  193. }
  194.  
  195. template< class _CLASS_, class _KEY_ >
  196. std::set< _CLASS_* >
  197. UniqueKeyCache< _CLASS_, _KEY_ >::Items( ) const
  198. {
  199.  std::set< _CLASS_* > to_return;
  200.  
  201.  for ( auto& pair : _items )
  202.    to_return.insert( pair.second->Item( ) );
  203.  
  204.  return to_return;
  205. }
  206.  
  207. template< class _CLASS_, class _KEY_ >
  208. std::map< _KEY_, _CLASS_* >
  209. UniqueKeyCache< _CLASS_, _KEY_ >::MappedItems( ) const
  210. {
  211.  std::map< _KEY_, _CLASS_* > to_return;
  212.  
  213.  for ( auto& pair : _items )
  214.    to_return.insert( std::make_pair( pair.first, pair.second->Item( ) ) );
  215.  
  216.  return to_return;
  217. }
  218.  
  219. template< class _CLASS_, class _KEY_ >
  220. _CLASS_*
  221. UniqueKeyCache< _CLASS_, _KEY_ >::Item(
  222.          const _KEY_& ar_key ) const
  223. {
  224.  auto it = _items.find( ar_key );
  225.  if ( it != _items.end( ) )
  226.    return it->second->Item( );
  227.  
  228.  return nullptr;
  229. }
  230.  
  231. template< class _CLASS_, class _KEY_ >
  232. std::set< _CLASS_* >
  233. UniqueKeyCache< _CLASS_, _KEY_ >::ToCreate( ) const
  234. {
  235.  std::set< _CLASS_* > to_return;
  236.  
  237.  for ( auto& pair : _items )
  238.  {
  239.    if ( pair.second->IsNew( ) )
  240.      to_return.insert( pair.second->Item( ) );
  241.  }
  242.  
  243.  return to_return;
  244. }
  245.  
  246. template< class _CLASS_, class _KEY_ >
  247. std::map< _KEY_, _CLASS_* >
  248. UniqueKeyCache< _CLASS_, _KEY_ >::ToUpdate( ) const
  249. {
  250.  std::map< _KEY_, _CLASS_* > to_return;
  251.  
  252.  for ( auto& item : _items )
  253.  {
  254.    if ( item.second->IsModified( ) )
  255.      to_return.insert( std::make_pair( item.first, item.second->Item( ) ) );
  256.  }
  257.  
  258.  return to_return;
  259. }
  260.  
  261. template< class _CLASS_, class _KEY_ >
  262. std::map< _KEY_, _CLASS_* >
  263. UniqueKeyCache< _CLASS_, _KEY_ >::ToDelete( ) const
  264. {
  265.  std::map< _KEY_, _CLASS_* > to_return;
  266.  
  267.  for ( auto& pair : _deletedItems )
  268.    to_return.insert( std::make_pair( pair.first, pair.second->Item( ) ) );
  269.  
  270.  return to_return;
  271. }
  272.  
  273. template< class _CLASS_, class _KEY_ >
  274. bool
  275. UniqueKeyCache< _CLASS_, _KEY_ >::ChangeKey(
  276.          const _CLASS_* ap_element,
  277.          const _KEY_& ar_key )
  278. {
  279.  if ( ap_element )
  280.  {
  281.    auto it = _items.find( ar_key );
  282.    if ( it != _items.end( ) )
  283.      return ( it->second->Item( ) == ap_element );
  284.  
  285.    it = FindItem( *ap_element );
  286.    if ( it != _items.end( ) )
  287.    {
  288.      it->second->SetKey( ar_key );
  289.      _items.insert( std::make_pair( ar_key, std::move( it->second ) ) );
  290.      _items.erase( it );
  291.      return true;
  292.    }
  293.  }
  294.  
  295.  return false;
  296. }
  297.  
  298. template< class _CLASS_, class _KEY_ >
  299. void
  300. UniqueKeyCache< _CLASS_, _KEY_ >::Commit( )
  301. {
  302.  _deletedItems.clear( );
  303.  
  304.  for ( auto& item : _items )
  305.    item.second->Commit( );
  306. }
  307.  
  308. template< class _CLASS_, class _KEY_ >
  309. void
  310. UniqueKeyCache< _CLASS_, _KEY_ >::Clear( )
  311. {
  312.  _items.clear( );
  313.  _deletedItems.clear( );
  314. }
  315.  
  316. template< class _CLASS_, class _KEY_ >
  317. bool
  318. UniqueKeyCache< _CLASS_, _KEY_ >::ChangesPending( ) const
  319. {
  320.  if ( !_deletedItems.empty( ) )
  321.    return true;
  322.  
  323.  for ( auto& item : _items )
  324.  {
  325.    if ( item.second->IsModified( ) )
  326.      return true;
  327.  }
  328.  
  329.  return false;
  330. }



Posibles mejoras

La cache se puede heredar para añadir funcionalidad específica en función del tipo de objeto que estemos cacheando. Por ejemplo, en el caso de los usuarios podría ser util obtener un listado de los usuarios con la cuenta bloqueada. Básicamente lo único que tendríamos que modificar en la clase 'UniqueKeyCache' es poner el listado de elementos en la parte protegida y heredar:

Código
  1. class UsersCache : public UniqueKeyCache< User, int >
  2. {
  3.  public:
  4.  
  5.    std::set< User* > LockedAccounts( ) const
  6.    {
  7.      std::set< User* > to_return;
  8.  
  9.      for ( auto& pair : _items )
  10.      {
  11.        if ( pair->second->Item( )->IsLocked( ) )
  12.          to_return.insert( pair->second->Item( ) );
  13.      }
  14.  
  15.      return to_return;
  16.    }
  17. };

 Y bueno, dado que un template no es exactamente lo mismo que una clase base, lo mejor es también sustituir 'DBUsers' para que acepte un objeto de tipo 'UserCache':
 
Código
  1. class DBUsers
  2. {
  3.    DBUsers( ) = delete;
  4.  
  5.    DBUsers(
  6.          const DBUsers& ar_other ) = delete;
  7.  
  8.    const DBUsers& operator=(
  9.          const DBUsers& ar_other ) = delete;
  10.  public:
  11.  
  12.    DBUsers(
  13.           const UsersCache& ar_cache )
  14.      : _cache{ ar_cache }
  15.    {
  16.    }
  17.  
  18.    virtual ~DBUsers( )
  19.    {
  20.    }
  21.  
  22.    bool SaveData( )
  23.    {
  24.      bool ok = true;
  25.  
  26.      // Este primer chequeo no sería necesario, pero me gusta la simetría
  27.      // y además así aseguro que se elimina deletedItems cuando deja de ser necesario
  28.      if ( ok )
  29.      {
  30.        // Primero es recomendable realizar la operación de borrado
  31.        auto deletedItems = _cache.ItemsToDelete( );
  32.        for ( auto& pair : deletedItems )
  33.        {
  34.          // Generación y ejecución de las consultas de eliminación.
  35.          // Si algúna consulta da error, actualizamos ok a false
  36.        }
  37.      }
  38.  
  39.      if ( ok )
  40.      {
  41.        // Después actualizamos los elementos modificados
  42.        auto updatedItems = _cache.ItemsToUpdate( );
  43.        for ( auto& pair : updatedItems )
  44.        {
  45.          // Generación y ejecución de las consultas de actualización
  46.          // Si algúna consulta da error, actualizamos ok a false
  47.        }
  48.      }
  49.  
  50.      // Finalmente damos de alta los nuevos registros
  51.      if ( ok )
  52.      {
  53.        auto newItems = _cache.ItemsToCreate( );
  54.        for ( auto& item : newItems )
  55.        {
  56.          // Generación y ejecución de las consultas de creación
  57.          // Si algúna consulta da error, actualizamos ok a false
  58.  
  59.          // No hay que olvidarse de recuperar el OID de cada elemento
  60.          // y actualizar la cache.
  61.          _cache.ChangeKey( &item, newOid );
  62.        }
  63.      }
  64.  
  65.      if ( ok )
  66.        _cache.Commit( );
  67.      else
  68.      {
  69.        // Si se produce algún error tenemos que decidir entre resetear la cache
  70.        // o permitir al usuario intentarlo más veces.
  71.      }
  72.    }
  73.  
  74.    void LoadUsers( )
  75.    {
  76.      // Consulta de selección sobre la tabla de usuarios
  77.      // ...
  78.      while ( row.next( ) ) // Para cada registro leído...
  79.      {
  80.        User* user = new User{ };
  81.        user->SetName( row.data( "name" ).toString( ) );
  82.        user->SetSurname( row.data( "surname" ).toString( ) );
  83.  
  84.        _cache.Add( user, row.data( "id" ).toInt( ) );
  85.      }
  86.    }
  87.  
  88.    // No hace falta leer toda la tabla... se puede leer bajo demanda para
  89.    // reducir los tiempos de acceso.
  90.    void LoadUsers( const std::string& name );
  91.  
  92.  private:
  93.  
  94.    UsersCache& _cache;
  95.  
  96. };

Y esto es todo. Creo que el diseño final es elegante, ya que reduce enormemente la dependencia entre clases y permite crear rápidamente enlaces con bases de datos, ficheros XML, ficheros binarios, sockets, etc.

También hay que tener en cuenta que el diseño que he planteado se puede adaptar de forma más o menos sencilla para que funcione bajo diferentes escenarios: Registros sin clave primaria, Registros con clave primaria compuesta, Clave primaria repetida, etc. Yo, personalmente, diseñaría un template de cache específico para cada caso... no soy especialmente partidario de hacer un template que herede de otro.

Y poco más que contar. Espero que os haya gustado, que hayáis aprendido algo o, en el peor de los casos, me conformo con que no penséis que habéis perdido el tiempo al leer este artículo.

Un saludo.
243  Programación / Programación C/C++ / Re: Duda básica sobre C: en: 9 Septiembre 2014, 09:07 am
Yo algunas cosillas que he programado en c++ y lo que he aprendido en conclase, para las frases y demás utilizaba el tipo char, aunque más tarde me entere del tipo string en este foro, y me dijeron que era más correcto, por eso me gustaría saber la diferencia

Un "string" al estilo C puede estar definido de las siguientes maneras:

Código
  1. // Opción 1
  2. // Cadena de sólo lectura. Modificar su contenido puede provocar fallos en la aplicación.
  3. char *cadena = "Una frase cualquiera";
  4.  
  5. // Opción 2
  6. // Cadena de tamaño dinámico. Es la opción más flexible aunque puede provocar fugas de memoria
  7. // si no se hace con cuidado.
  8. char *cadena = malloc( x );
  9.  
  10. // Opción 3
  11. // Cadena de longitud fija. Es un diseño demasiado rígido ya que no te permite adaptarte.
  12. // Es esa longitud y punto, no hay más.
  13. char cadena[20];

C++ proporciona la clase "string", esta clase te proporciona la misma funcionalidad que el segundo caso con la ventaja de despreocuparte por el uso de memoria, es decir, te olvidas de malloc, free y realloc:

Un ejemplo en C:

Código
  1. char * cadena = malloc( 10 );
  2. strcpy( cadena, "123456789" );
  3. printf( "%s\n", cadena );
  4.  
  5. cadena = realloc( cadena, 20 * sizeof(char) );
  6. strcat( cadena, "0123456789" );
  7. printf( "%s\n", cadena );
  8.  
  9. free( cadena );

Equivalente en C++:
Código
  1. std::string cadena;
  2. cadena = "123456789";
  3. std::cout << cadena << std::endl;
  4.  
  5. cadena += "0123456789";
  6. std::cout << cadena << std::endl;

C++ te permite no "reinventar la rueda" cada dos por tres. Esto es a costa de delegar en las clases parte de la funcionalidad. Esto tiene la ventaja de que hay que escribir menos código y éste es menos propenso a errores... la parte negativa es que el código resultante es más lento. Eso sí, dudo mucho que seas capaz de notar la diferencia de velocidad a simple vista entre las dos versiones.
244  Programación / Programación C/C++ / Re: Ejercicio, Programación Orientada a Objetos.. URGENTEE!! en: 9 Septiembre 2014, 08:44 am
Aparte de lo comentado por kutcher te pongo un par de comentarios adicionales:

* Las variables i, j, Num2 y Res deberían ser variables locales definidas en funciones y no en el cuerpo de la clase, ya que únicamente las usas para calcular resultados parciales.

* Para almacenar la secuencia binaria puedes usar la clase "string" o la clase "vector".

* A diferencia de C, en C++ no es necesario poner "void" en las funciones que no reciben argumentos.

* Si se llama a "Imprimir" sin llamar antes a "Leer" vas a imprimir basura. Para evitar eso necesitas hacer uso del constructor por defecto. El constructor se llama automáticamente al crear el objeto, por lo que es un lugar ideal para inicializar las variables de la clase.

Código
  1. #include <iostream>
  2. #include <string>
  3.  
  4. using namespace std;
  5.  
  6. class Decimal
  7. {
  8.  private:
  9.    int numeroDecimal;
  10.    std::string numeroBinario;
  11.  
  12.  public:
  13.  
  14.    // Constructor por defecto
  15.    Decimal( );
  16.  
  17.    void Leer();
  18.    void Proceso();
  19.    void Imprimir();
  20. };
  21.  
  22. // Implementación del constructor por defecto.
  23. Decimal::Decimal( )
  24. {
  25.  numeroDecimal = 0;
  26. }
  27.  
  28. void Decimal::Leer()
  29. {
  30.  cout<<"Ingrese el numero del que desea conocer su equivalencia en binario"<<endl;
  31.  cin>>numeroDecimal;
  32. }
  33.  
  34. void Decimal::Proceso()
  35. {
  36.  int numero = numeroDecimal;
  37.  
  38.  if ( numero == 0 )
  39.    numeroBinario = "0";
  40.  
  41.  while ( numero )
  42.  {
  43.    int digito = numero % 2;
  44.    numero /= 2;
  45.  
  46.    // Los nuevos valores los vamos añadiendo al inicio de la cadena para que la impresión del
  47.    // resultado sea más sencilla.
  48.    numeroBinario.insert( 0, 1, '0' + digito );
  49.  }
  50. }
  51.  
  52. void Decimal::Imprimir()
  53. {
  54.  cout<<"El numero "<< numeroDecimal <<" en binario es: " << numeroBinario << endl;
  55. }
245  Programación / Programación C/C++ / Re: Duda con anidamiento de "for": en: 5 Septiembre 2014, 11:10 am
Código
  1. int i, j;
  2. int total_exterior = 0;
  3. int total_interior = 0;
  4.  
  5. for ( i=0; i< 3; i++ ) // bucle exterior
  6. {
  7.  total_exterior++;
  8.  
  9.  for ( j=0; j<10; j++ ) // bucle interior
  10.  {
  11.    printf( "i=%d j=%d", i, j );
  12.    total_interior++;
  13.  }
  14. }
  15.  
  16. printf( "total_exterior = %d\n", total_exterior );
  17. printf( "total_interior = %d\n", total_interior );

Un bucle anidado no tiene mucho misterio... el primer bucle que se ejecuta es el exterior, y por cada iteración del bucle exterior se ejecuta el bucle interior en su totalidad.

En el ejemplo que te he puesto, el bucle exterior se ejecuta 3 veces, en cada una de estas iteraciones el bucle interior se ejecuta 10 veces. al final del proceso 'total_exterior' vale 3 (el bucle exterior se ha ejecutado únicamente 3 veces) y 'total_interior' vale 30 (por cada iteración del bucle exterior, el interior se ha ejecutado 10 veces, 3 * 10 = 30 ).
246  Programación / Programación C/C++ / Re: paso de vectores a una funcion en: 5 Septiembre 2014, 09:12 am
Tienes varias opciones a la hora de enfocar el problema:

Usar contenedores

Los contenedores de C++ son de los elementos más útiles en programación. Elegir el contenedor adecuado a cada situación puede ahorrarte mucho código.

En tu caso, imagina que tienes X candidatos. Podrías crear un vector de X+1 elementos, de tal forma que el índice cero se use para almacenar los votos nulos, el resto de índices se usa para contar los votos de cada candidatos:

Código
  1. // Este número lo calculas al leer el fichero
  2. int numCandidatos = 10;
  3.  
  4. // Creamos un vector que inicialmente va a tener X + 1 elementos
  5. // Al ser un vector de tipo int, el valor inicial de cada elemento es 0.
  6. std::vector< int > votos{ numCandidatos + 1 };
  7.  
  8. // Fase de votación
  9. std::cout << "Emite tu voto: ";
  10. int voto;
  11. std::cin >> voto;
  12.  
  13. if ( voto == 0 )
  14. {
  15.  // Fin de la votación
  16. }
  17. else if ( voto < 0 || voto >= votos.size( ) )
  18.  votos[ 0 ]++; // El voto es nulo
  19. else
  20.  votos[ voto ]++; // Incrementamos los votos del candidato

Con este formato, la presentación de los resultados es bastante trivial:

Código
  1. for ( unsigned int i = 1; i < votos.size( ); ++i )
  2. {
  3.  std:cout << "Candidato " << i << ": " << votos[ i ] << " votos" << std::endl;
  4. }
  5.  
  6. std::cout << "Votos nulos: " << votos[ 0 ] << std::endl;
  7.  
  8. // Total de votos versión 1:
  9. int total = 0;
  10. for ( auto it = std::begin( votos ); it != std::end( votos ); ++it )
  11.  total += *it;
  12.  
  13. // Total de votos version 2:
  14. int total = std::accumulate( std::begin( vector ), std::end( vector ), 0 );
  15.  
  16. std::cout << "Total de votos: " << total << std::endl;

Otro contenedor que puedes usar es 'map'. Los mapas tienen la ventaja de que pueden usar un string por índice (y quien dice un string dice un float, una clase propia, ... ), lo cual viene bien en bastantes ocasiones.

También puedes usar new y delete para crear un arreglo. A mi no me parece una buena idea salvo que quieras practicar con ello.
247  Programación / Programación C/C++ / Re: Array con numeros primos. en: 5 Septiembre 2014, 08:37 am
Bienvenido al foro.

Te pongo las cosillas que he ido viendo al revisar tu código.

1. Estás usando cabeceras de C y de C++

Código
  1. #include <iostream>
  2. #include <stdlib.h>

La cabecera 'iostream' es propia de C++, sin embargo 'stdlib.h' es de C. Salvo que no haya alternativas es mejor no mezclar. Esto no es un error en sí, es una recomendación.

Además, no veo motivos para tener el include de 'stdlib.h', ya que no estás accediendo a ninguna función declarada en dicha librería. Tener includes innecesarios incrementa el tiempo de compilación, aunque en este ejemplo no se note.

2. Tener conceptos claros

Código
  1. for(x=0;x<100;x++)  // recorro y lleno el arreglo con los # del 1 al 100
  2.   {
  3.       array_prime[x]=num++;
  4.   }

Sobre las líneas de arriba te puedo decir dos cosas:

* 'num' es una variable que te puedes ahorrar. De hecho 'num=x+1' durante todo el ciclo.
* 'array_prime' también te la puedes ahorrar. Si te das cuenta, en la línea 25 ocurre que 'array_prime[ x ]==x+1', luego puedes hacer la sustitución y ahorrarte tanto el arreglo como el bucle que tienes destacado en este apartado.

3. Tienes que aprender a depurar el código.

Código
  1. for(x=0;x<100;x++) //Recorro el arreglo
  2.     {
  3.       for(z=1;z<=x;z++) // for para probar cada numero del arreglo es primo
  4.       {
  5.         if(x%z==0)add++;  // si el residuo es 0, contador add cuenta.
  6.       }
  7.       if(add==2)  // si el contador es igual a 2, el #del arreglo es primo.
  8.       {
  9.          cout<<array_prime[x]; //asigno el #primo al array. imprimo
  10.       }
  11.     }

El error en sí se encuentra en estas líneas... para cada número usas 'add' para contar sus divisores... pero no reinicias el valor de 'add' en ningún momento. Luego su valor no hará sino crecer de forma constante:

* x = 0 -> add = 0
* x = 1 - divisores: 1 -> add = 0 + 1 = 1
* x = 2 - divisores: 1, 2 -> add = 1 + 2 = 3
* x = 3 - divisores: 1, 3 -> add = 3 + 2 = 5
* x = 4 - divisores: 1, 2, 4 -> add = 5 + 3 = 8
* x = 5 - divisores: 1, 5 -> add = 8 + 2 = 10
...

Además, fíjate que no estás comparando los mismos números:

Código
  1. for(x=0;x<100;x++)
  2.     {
  3.       for(z=1;z<=x;z++)
  4.       {
  5.         if(x%z==0)add++;  // AQUI estas mirando si 'x' es primo
  6.       }
  7.       if(add==2)
  8.       {
  9.          cout<<array_prime[x]; // si 'x' es primo, imprimes 'array_prime[x]', que es 'x+1'
  10.       }
  11.     }

Son errores tontos, pero sabiendo usar un depurador los encuentras con cierta facilidad.

A continuación te pongo tu código con algún retoque. He intentado mantener el código original. Como ves te estabas complicando en exceso.

Código
  1. /* Escribir un programa que almacene en un arreglo los
  2.    números primos comprendidos entre 1 y 100. */
  3.  
  4. #include <iostream>
  5. using namespace std;
  6.  
  7. int main()
  8. {
  9.   for( int x=2; x<100; x++ ) //Recorro el arreglo
  10.   {
  11.     int add = 0;
  12.  
  13.     for( int z=1; z<=x; z++ ) // for para probar cada numero del arreglo es primo
  14.     {
  15.       if(x%z==0)add++;  // si el residuo es 0, contador add cuenta.
  16.     }
  17.  
  18.     if(add==2)  // si el contador es igual a 2, el #del arreglo es primo.
  19.     {
  20.        cout << x << " "; //asigno el #primo al array. imprimo
  21.     }
  22.   }
  23.    cin.get();cin.get();
  24.    return 0;
  25. }

Aún así has de saber que tu código está trabajando más de lo necesario para encontrar primos. En el mismo momento en el que encuentras un divisor, el número ya no es primo, por lo que no hace falta que sigas comprobando más combinaciones; además, para saber si un número es primo no hace falta comprobar todo el rango [2, x-1], con comprobar el rango [2, x/2] te sobra. Este rango se podría reducir aún más, pero habría que hacer algún cambio más en el código.

Después de estas pequeñas optimizaciones el código podría lucir tal que:

Código
  1. /* Escribir un programa que almacene en un arreglo los
  2.    números primos comprendidos entre 1 y 100. */
  3.  
  4. #include <iostream>
  5. using namespace std;
  6.  
  7. int main()
  8. {
  9.   for( int x=2; x<100; x++ ) //Recorro el arreglo
  10.   {
  11.     bool primo = true;
  12.  
  13.     int max = x/2;
  14.     for( int z=2; z<=max; z++ )
  15.     {
  16.       if(x%z==0) // Si es divisor, no es primo.
  17.       {
  18.         primo = false;
  19.         break;
  20.       }
  21.     }
  22.  
  23.     if(primo) // Si es primo, lo saco por pantalla.
  24.     {
  25.        cout << x << " ";
  26.     }
  27.   }
  28.    cin.get();cin.get();
  29.    return 0;
  30. }
248  Programación / Programación C/C++ / Re: Escritura y lectura de archivos binarios en: 4 Septiembre 2014, 09:51 am
Código
  1. if((fichero=fopen(name,"rb"))!=NULL){
  2.  do{
  3.    printf("\nIntroduzca numero de persona a visualizar: ");
  4.    // ...
  5.  
  6.    for(i=0;i<N;i++){
  7.      if(numero==i){
  8.        printf("\nNombre: %s",persona[i].nombre);
  9.        printf("\nCiudad: %s\n",persona[i].ciudad);
  10.        printf("Edad: %d",persona[i].edad);
  11.        leo=fread(&persona[i],1,sizeof(persona),fichero);
  12.      }
  13.    }
  14.  }while(leo!=0);
  15.  fclose(fichero);
  16. }

No me ha quedado muy clara esta parte del código.

Intentas leer un registro determinado del archivo... pero el mecanismo es extraño. Me explico:

1. Abres el archivo
2. Preguntas al usuario el índice del registro a visualizar??? antes de haber leído el archivo ?????
3. Después, localizas ese registro, lo sacas por pantalla y entonces sobreescribes su contenido con el primer registro que te encuentras en el fichero. Al final lo que acabas de leer te lo guardas para ti y no lo sacas por pantalla.
4. Vuelves al paso 2 hasta que hayas leído todos los registros del archivo.

El código de arriba lo puedes simplificar. Quizás te ayude a ver que no tiene mucho sentido :

Código
  1. if((fichero=fopen(name,"rb"))!=NULL){
  2.  do{
  3.    printf("\nIntroduzca numero de persona a visualizar: ");
  4.    // ...
  5.  
  6.    printf("\nNombre: %s",persona[numero].nombre);
  7.    printf("\nCiudad: %s\n",persona[numero].ciudad);
  8.    printf("Edad: %d",persona[numero].edad);
  9.    leo=fread(&persona[numero],1,sizeof(persona),fichero);
  10.  }while(leo!=0);
  11.  fclose(fichero);
  12. }

El comportamiento es francamente extraño.

Quizás deberías comentar qué se espera acerca del funcionamiento de este programa.
249  Programación / Programación C/C++ / Re: Programa para detectar palindromos en: 3 Septiembre 2014, 15:02 pm
Para las cadenas, usa la clase string en vez de char*:

Código
  1. #include <string>
  2.  
  3. // const& implica una referencia constante
  4. bool palindromo( const std::string& cadena );
  5.  
  6. // ...
  7.  
  8. std::string cadena;
  9.  
  10. // ...
  11.  
  12. std::cin >> cadena;
  13.  
  14. // ...

Además, te complicas demasiado la forma de recorrer la cadena:

Código
  1. int j = sizeof(cadena)/sizeof(cadena[0]);

sizeof(cadena) te va a dar 100... realmente tiene 100 caracteres tu cadena???, no verdad??

Código
  1. int j = strlen(cadena) - 1;

O si estás haciendo uso de la clase string:

Código
  1. int j = cadena.length( ) - 1;

Por otro lado, haces un uso un tanto complicado de la variable 'n':

* No la inicializas al principio... eso es peligroso
* Solo va a tener dos posibles estados... mejor declararla como bool
* Poner nombres significativos a las variables facilita la lectura y mantenimiento del código.

Código
  1. bool esPalindromo = true;
  2.  
  3. do
  4. {
  5.  if ( cadena[ k ] != cadena[ j ] )
  6.    esPalindromo = false;
  7.  else
  8.  {
  9.    k++;
  10.    j--;
  11.  }
  12. } while ( k < j && esPalindromo ); // k != j - 1 ¿¿??

Aún con todo, insisto, hay formas más sencillas de hacer esto. Por ejemplo copiar la cadena invertida en otro buffer (o en otra instancia de tipo string) y comparar ambas cadenas (con strcmp o con el operador == según el caso )... si son iguales entonces hay palíndromo:

Código
  1. int main( )
  2. {
  3.  std::string cadena;
  4.  
  5.  std::cout << "Introduce una cadena: " << std::endl;
  6.  
  7.  std::cin >> cadena;
  8.  
  9.  std::string cadenaInvertida{ cadena.rbegin( ), cadena.rend( ) };
  10.  
  11.  if ( cadena == cadenaInvertida )
  12.    std::cout << "Es palíndromo" << std::endl;
  13.  else
  14.    std::cout << "NO es palíndromo" << std::endl;
  15. }



PD.: Este es mi mensaje nº 1.000 yuhuuuu XDDD
250  Programación / Programación C/C++ / Re: Keyloggers en C o en C++?? en: 3 Septiembre 2014, 09:43 am
Alguien me puede explicar porque C es mejor para los keyloggers? Si puedes hacer lo mismo en c++ ??

Saludos

No hay diferencias... quizás un poco en rendimiento... pero no creo que eso sea crítico en un keylogger... no hay más que ver que poca gente alcanza las 400 ppm y eso no es ningún reto para un programa normal.


Páginas: 1 ... 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 [25] 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 ... 125
WAP2 - Aviso Legal - Powered by SMF 1.1.21 | SMF © 2006-2008, Simple Machines