I decided to play around a bit last week trying to create a more standard set of C++ bindings for libcouchbase.
While libcouchbase is C and is thus fully usable from C++, I had a common and frequent itch to scratch when using libcouchbase in C++ programs — namely there is no class hierarchy for command or response objects and the libcouchbase interface hardly feels “friendly”. So I set out on a pet project to make a standard and generic (in the colloquial sense of the term) interface for libcouchbase in C++. This is a work in progress and may be found at https://github.com/couchbaselabs/lcb-cxx.
Because maximum compatibility for C++ was desired, I stayed away from using large external libraries like boost or C++11. These extra bindings can always be layered on top of the “generic” C++ bindings, while reversing the process may be more difficult.
The result was a set of bindings that exposed the following semantics:
Command Objects
All commands inherit from a Command object; e.g:
class Command { };
All key commands (i.e. set, get, delete) inherit from a KeyCommand object which derives from Command. The KeyCommand object has accessors for key and hashkey, e.g.
virtual void setKey(const std::string&) = 0;
virtual void setHashKey(const std::string&) = 0;
}
Three template classes were created to aid with the construction of the command objects. They are instantiated with their T being the C libcouchbase structure which they wrap as their only data member – – and thus ensuring that the performance and memory profile of the C++ command class is more or less the same as the C struct (though there is a vtable). These templates provide common setters for commands which accept CAS and/or an expiration time, e.g.
public:
void setKey(const std::string &s) {
cmd.v.v0.key = s.c_str();
cmd.v.v0.nkey = s.size();
}
// …
private:
T cmd;
};
Response Objects
Like command objects, the response objects too are provided in a hierarchy; they contain the C lcb_resp_t * as their only data member. Their hierarchy is as follows:
- The abstract ResponseBase class. This features accessors for keys
- The abstract CasResponseBase class which inherits ResponseBase and provides accessors for the CAS
- The Response
class which implements ResponseBase retrieving the key information from T::v.v0.key - The CasResponse
and implements CasResponseBase by providing cas via T::v.v0.cas - Response-specific classes which provide extra information; e.g. GetResponse is implemented as CasResponse
with additional accessors for the value and flags.
In the header, this looks something like this:
public:
virtual const void *getKey(lcb_size_t *n) const = 0;
std::string getKey() const;
};
class Response : public I {
public:
typedef T LcbInternalResponse;
virtual const void * getKey(lcb_size_t *n) const {
*n = resp–>v.v0.nkey;
return resp–>v.v0.key;
}
protected:
const T * resp;
};
class CasResponse : public Response<T, CasResponseBase>
{
public:
virtual lcb_cas_t getCas() const {
return Response<T,CasResponseBase>::getRawResponse()–>v.v0.cas;
}
};
public:
lcb_uint64_t getValue() const { return getRawResponse()–>v.v0.value; }
};
Since the response classes are both derived from the templates as well as their pure abstract bases, they can have their common members treated by helper methods and classes, so for example, for a given function called logKey which logs the key from the response, it may be implemented like so:
std::cout << resp–>getKey() << std::endl;
}
And then logKey may be called with any response object.
Callbacks
Finally I've also implemented a callback interface. Since libcouchbase is a C library and uses C callbacks, the following boilerplate for C++ code was rather common
static void arith_handler(lcb_t, const void *cookie, lcb_error_t err, const lcb_arithmetic_response_t *resp) {
MyCppObject *o = reinterpret_cast<MyCppObject>(const_cast<void*>(cookie));
o–>doSomething(resp);
}
} // extern “C”
then, to set the callback
In the new bindings, callbacks are exposed in a unified object; so that you don't have to explicitly set handlers – but simply subclass the ResponseHandler class and implement the onArithmetic(OperationContext *, const ArithmeticResponse *, lcb_error_t) method.
If you don't wish to implement dedicated handlers for generic commands, you can simply implement the onDefault(OperationContext *, const ResponseBase *, lcb_error_t) and handle all commands from there.
[…] Blog Post of the Week #2 : libcouchbase with C++ and threads (1/2)Question of the Week : Question about the time cost of swap rebalance […]