Decidi brincar um pouco na semana passada, tentando criar um conjunto mais padrão de ligações C++ para a libcouchbase.
Embora a libcouchbase seja C e, portanto, totalmente utilizável em C++, eu tinha uma coceira comum e frequente para coçar quando usava a libcouchbase em programas C++ - ou seja, não há hierarquia de classes para objetos de comando ou resposta e a interface da libcouchbase dificilmente parece "amigável". Por isso, iniciei um projeto para criar uma interface padrão e genérica (no sentido coloquial do termo) para o libcouchbase em C++. Esse é um trabalho em andamento e pode ser encontrado em https://github.com/couchbaselabs/lcb-cxx.
Como a compatibilidade máxima com o C++ era desejada, evitei usar bibliotecas externas grandes, como impulso ou C++11. Essas ligações extras sempre podem ser colocadas em camadas sobre as ligações "genéricas" do C++, mas reverter o processo pode ser mais difícil.
O resultado foi um conjunto de associações que expôs a seguinte semântica:
Objetos de comando
Todos os comandos são herdados de um objeto Command; por exemplo
class Command { };
Todos os comandos de chave (ou seja, set, get, delete) são herdados de um KeyCommand que deriva do objeto Comando. O KeyCommand tem acessores para key e hashkey, por exemplo
virtual vazio setKey(const padrão::string&) = 0;
virtual vazio setHashKey(const padrão::string&) = 0;
}
Três classes de modelo foram criadas para ajudar na construção dos objetos de comando. Elas são instanciadas com seu T sendo a estrutura libcouchbase do C, que elas envolvem como seu único membro de dados, garantindo assim que o desempenho e o perfil de memória da classe de comando do C++ sejam mais ou menos os mesmos da estrutura do C (embora haja uma vtable). Esses modelos fornecem definidores comuns para comandos que aceitam CAS e/ou um tempo de expiração, por exemplo
público:
vazio setKey(const padrão::string &s) {
cmd.v.v0.chave = s.c_str();
cmd.v.v0.nkey = s.tamanho();
}
// …
privado:
T cmd;
};
Objetos de resposta
Assim como os objetos de comando, os objetos de resposta também são fornecidos em uma hierarquia; eles contêm o C lcb_resp_t * como seu único membro de dados. Sua hierarquia é a seguinte:
- O resumo ResponseBase classe. Ela apresenta acessores para chaves
- O resumo CasResponseBase que herda a classe ResponseBase e fornece acessores para o CAS
- O Resposta
classe que implementa o ResponseBase, recuperando as informações de chave do T::v.v0.key - O CasResponse
e implementa o CasResponseBase, fornecendo cas via T::v.v0.cas - Classes específicas de resposta que fornecem informações adicionais; por exemplo GetResponse é implementado como CasResponse
com acessores adicionais para o valor e os sinalizadores.
No cabeçalho, isso se parece com o seguinte:
público:
virtual const vazio *getKey(lcb_size_t *n) const = 0;
padrão::string getKey() const;
};
classe Resposta : público I {
público:
typedef T LcbInternalResponse;
virtual const vazio * getKey(lcb_size_t *n) const {
*n = resp–>v.v0.nkey;
retorno resp–>v.v0.chave;
}
protegida:
const T * resp;
};
classe CasResponse : público Resposta<T, CasResponseBase>
{
público:
virtual lcb_cas_t getCas() const {
retorno Resposta<T,CasResponseBase>::getRawResponse()–>v.v0.cas;
}
};
público:
lcb_uint64_t getValue() const { retorno getRawResponse()–>v.v0.valor; }
};
Como as classes de resposta são derivadas dos modelos e de suas bases abstratas puras, elas podem ter seus membros comuns tratados por métodos e classes auxiliares, de modo que, por exemplo, para uma determinada função chamada logKey, que registra a chave da resposta, ela pode ser implementada assim:
padrão::cout << resp–>getKey() << padrão::endl;
}
E, em seguida, logKey pode ser chamado com qualquer objeto de resposta.
Chamadas de retorno
Por fim, também implementei uma interface de retorno de chamada. Como a libcouchbase é uma biblioteca em C e usa retornos de chamada em C, o seguinte boilerplate para código C++ era bastante comum
estático vazio manipulador de aritmética(lcb_t, const vazio *cookie, lcb_error_t err, const lcb_arithmetic_response_t *resp) {
MyCppObject *o = reinterpretar_cast<MyCppObject>(const_cast<vazio*>(biscoito));
o–>doSomething(resp);
}
} // extern "C"
em seguida, para definir o retorno de chamada
Nas novas associações, os retornos de chamada são expostos em um objeto unificado, de modo que você não precisa definir explicitamente os manipuladores, mas simplesmente subclasse a classe ResponseHandler e implementar o onArithmetic(OperationContext *, const ArithmeticResponse *, lcb_error_t) método.
Se não quiser implementar manipuladores dedicados para comandos genéricos, você pode simplesmente implementar o onDefault(OperationContext *, const ResponseBase *, lcb_error_t) e manipular todos os comandos a partir daí.
[...] Postagem de blog da semana #2: libcouchbase com C++ e threads (1/2)Pergunta da semana: Pergunta sobre o custo de tempo do rebalanceamento de swap [...]