Decidí jugar un poco la semana pasada tratando de crear un conjunto más estándar de enlaces C ++ para libcouchbase.

Aunque libcouchbase es C y por lo tanto es totalmente utilizable desde C++, yo tenía un prurito común y frecuente que rascar cuando usaba libcouchbase en programas C++ - a saber, no hay jerarquía de clases para objetos de comando o respuesta y la interfaz de libcouchbase difícilmente se siente "amigable". Así que me embarqué en un proyecto para hacer una interfaz estándar y genérica (en el sentido coloquial del término) para libcouchbase en C++. Este es un trabajo en progreso y se puede encontrar en https://github.com/couchbaselabs/lcb-cxx.

Dado que se buscaba la máxima compatibilidad con C++, evité utilizar grandes bibliotecas externas como impulsar o C++11. Estos bindings adicionales siempre pueden superponerse a los bindings C++ "genéricos", mientras que invertir el proceso puede resultar más difícil.

El resultado fue un conjunto de enlaces que exponían la siguiente semántica:

Objetos de mando

Todos los comandos heredan de un objeto Command; por ejemplo

clase Comando { };

Todos los comandos de teclas (es decir, set, get, delete) heredan de un comando KeyCommand que deriva de Comando. En KeyCommand dispone de accesores para key y hashkey, por ejemplo

 
clase KeyCommand : público Comando {
    virtual void setKey(const std::cadena&) = 0;
    virtual void setHashKey(const std::cadena&) = 0;
}

Se han creado tres clases de plantillas para facilitar la construcción de los objetos comando. Se instancian con su T siendo la estructura C libcouchbase que envuelven como su único miembro de datos - - y asegurando así que el rendimiento y el perfil de memoria de la clase de comando C++ es más o menos el mismo que la estructura C (aunque hay una vtable). Estas plantillas proporcionan setters comunes para comandos que aceptan CAS y/o un tiempo de expiración, por ejemplo

 
plantilla <typename T> KeyCommand_v0 {
público:
       void setKey(const std::cadena &s) {
           cmd.v.v0.clave = s.c_str();
           cmd.v.v0.nkey = s.talla();
      }
      // …
privado:
    T cmd;
};
Objetos de respuesta

Al igual que los objetos de comando, los objetos de respuesta también se proporcionan en una jerarquía; contienen el C lcb_resp_t * como su único miembro de datos. Su jerarquía es la siguiente:

  1. Resumen ResponseBase clase. Esta clase dispone de accesorios para las claves
  2. Resumen CasResponseBase que hereda de ResponseBase y proporciona accesores para el CAS
  3. En Respuesta clase que implementa ResponseBase recuperando la información clave de T::v.v0.key
  4. En CasResponse que hereda de Respuesta e implementa CasResponseBase proporcionando cas a través de T::v.v0.cas
  5. Clases específicas de respuesta que proporcionan información adicional; por ejemplo GetResponse se implementa como CasResponse con accesores adicionales para el valor y las banderas.

En la cabecera, tiene este aspecto:

 
clase ResponseBase {
público:
    virtual const void *getKey(lcb_size_t *n) const = 0;
    std::cadena getKey() const;
};
plantilla <typename T, clase I>
clase Respuesta : público I {
público:
    typedef T LcbInternalResponse;
    virtual const void * getKey(lcb_size_t *n) const {
        *n = resp>v.v0.nkey;
        devolver resp>v.v0.clave;
    }
protegido:
    const T * resp;
};
plantilla <typename T>
clase CasResponse : público Respuesta<T, CasResponseBase>
{
público:
    virtual lcb_cas_t getCas() const {
        devolver Respuesta<T,CasResponseBase>::getRawResponse()>v.v0.cas;
    }
};
 
clase ArithmeticResponse : público CasResponse<C_ArithResp> {
público:
    lcb_uint64_t getValue() const { devolver getRawResponse()>v.v0.valor; }
};
 
 

Dado que las clases response derivan tanto de las plantillas como de sus bases abstractas puras, pueden tener sus miembros comunes tratados por métodos y clases helper, así por ejemplo, para una función dada llamada logKey que registra la clave de la respuesta, puede implementarse así:

 
void logKey(ResponseBase *resp) {
   std::cout << resp>getKey() << std::endl;
}

Y entonces logKey puede ser llamado con cualquier objeto de respuesta.

Devoluciones de llamada

Por último, también he implementado una interfaz de devolución de llamada. Como libcouchbase es una librería en C y usa callbacks en C, el siguiente boilerplate para código C++ era bastante común

externo "C" {
estático void arith_handler(lcb_t, const void *cookie, lcb_error_t err, const lcb_arithmetic_response_t *resp) {
    MyCppObject *o = reinterpretar_cast<MyCppObject>(const_cast<void*>(galleta));
    o>hacerAlgo(resp);
}
} // extern "C"

a continuación, para establecer la devolución de llamada

lcb_set_arithmetic_callback(instancia, arith_handler);

En las nuevas vinculaciones, las retrollamadas se exponen en un objeto unificado, de modo que no es necesario configurar explícitamente los manejadores, sino que basta con subclasificar la clase ResponseHandler e implementar la función onArithmetic(OperationContext *, const ArithmeticResponse *, lcb_error_t) método.

Si no desea implementar manejadores dedicados para comandos genéricos, puede simplemente implementar la función onDefault(OperationContext *, const ResponseBase *, lcb_error_t) y manejar todos los comandos desde allí.

Autor

Publicado por Mark Nunberg, Ingeniero de Software, Couchbase

Mark Nunberg es un ingeniero de software que trabaja en Couchbase. Mantiene la biblioteca del cliente C (libcouchbase), así como el cliente Python. También desarrolló el cliente Perl (para su uso en su anterior empresa) - que inicialmente le llevó a trabajar en Couchbase. Antes de unirse a Couchbase, trabajó en sistemas de enrutamiento distribuidos y de alto rendimiento en una empresa de análisis de comercio electrónico. Mark estudió Lingüística en la Universidad Hebrea de Jerusalén.

1 Comentarios

  1. [...] Blog Post of the Week #2 : libcouchbase with C++ and threads (1/2)Question of the Week : Pregunta sobre el coste temporal del reequilibrio de swaps [...]

Dejar una respuesta