Статьи

Libcouchbase с C ++ и потоками (1/2)

Первоначально созданный Марком Нунбергом,

я решил немного поиграть на прошлой неделе, пытаясь создать более стандартный набор привязок C ++ для libcouchbase.

Хотя libcouchbase является C и, таким образом, полностью используется из C ++, у меня был общий и частый зуд при использовании libcouchbase в программах на C ++ — а именно, отсутствует иерархия классов для объектов команд или ответов, и интерфейс libcouchbase едва ли чувствует себя «дружественным». Поэтому я решил создать проект для стандартного и универсального (в разговорном смысле этого слова) интерфейса для libcouchbase в C ++. Эта работа находится в стадии разработки и может быть найдена по адресу https://github.com/couchbaselabs/lcb-cxx .

Поскольку требовалась максимальная совместимость для C ++, я избегал использования больших внешних библиотек, таких как boost или C ++ 11 . Эти дополнительные привязки всегда можно наложить поверх «общих» привязок C ++, в то время как процесс может быть более сложным.

Результатом стал набор связываний, которые раскрыли следующую семантику:

Командные объекты

Все команды наследуются от объекта Command; например:

команда класса {};

Все ключевые команды (т.е. set, get, delete) наследуются от объекта KeyCommand, который наследуется от Command . Объект KeyCommand имеет средства доступа для ключа и хэш-ключа, например

class KeyCommand : public Command {
    virtual void setKey(const std::string&) = 0;
    virtual void setHashKey(const std::string&) = 0;
}

Три шаблонных класса были созданы для помощи в создании объектов команд. Они создаются с их T, являющимся структурой libcouchbase C, которую они обертывают как единственный элемент данных — и, таким образом, гарантируют, что производительность и профиль памяти класса команд C ++ более или менее совпадают со структурой C (хотя существует виртуальные таблицы). Эти шаблоны предоставляют общие установщики для команд, которые принимают CAS и / или время истечения, например

template <typename T> KeyCommand_v0 {
public:
       void setKey(const std::string &s) {
           cmd.v.v0.key = s.c_str();
           cmd.v.v0.nkey = s.size();
      }
      // ...
private:
    T cmd;
};
Объекты ответа

Как и объекты команд, объекты ответа также представлены в иерархии; они содержат C lcb_resp_t * в качестве единственного элемента данных. Их иерархия выглядит следующим образом:

  1. Абстрактный класс ResponseBase . Это функции аксессуаров для ключей
  2. Абстрактный класс CasResponseBase, который наследует ResponseBase и предоставляет средства доступа для CAS
  3. Класс Response <T>, который реализует ResponseBase, получая ключевую информацию из T :: v.v0.key
  4. CasResponse <T> , который наследует от Response <T> и реализует CasResponseBase, предоставляя КАС с помощью T :: v.v0.cas
  5. Классы для ответов, которые предоставляют дополнительную информацию; Например, GetResponse реализован как CasResponse <lcb_get_cmd_t> с дополнительными средствами доступа для значения и флагов.

В шапке это выглядит примерно так:

class ResponseBase {
public:
    virtual const void *getKey(lcb_size_t *n) const = 0;
    std::string getKey() const;
};
template <typename T, class I>
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;
};
template <typename T>
class CasResponse : public Response<T, CasResponseBase>
{
public:
    virtual lcb_cas_t getCas() const {
        return Response<T,CasResponseBase>::getRawResponse()->v.v0.cas;
    }
};
class ArithmeticResponse : public CasResponse<C_ArithResp> {
public:
    lcb_uint64_t getValue() const { return getRawResponse()->v.v0.value; }
};

Так как классы ответов являются производными как от шаблонов, так и от их чистых абстрактных баз, их общие члены могут обрабатываться вспомогательными методами и классами, например, для заданной функции с именем logKey, которая записывает ключ из ответа, он может быть реализовано так:

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

И тогда logKey может быть вызван с любым объектом ответа.

 

Callbacks

Наконец, я также реализовал интерфейс обратного вызова. Поскольку libcouchbase является библиотекой C и использует обратные вызовы C, следующий шаблон для кода C ++ был довольно распространенным:

extern "C" {
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"

затем, чтобы установить обратный вызов

lcb_set_arithmetic_callback(instance, arith_handler);

В новых привязках обратные вызовы отображаются в едином объекте; так что вам не нужно явно устанавливать обработчики, а просто создать подкласс класса ResponseHandler и реализовать метод onArithmetic (OperationContext *, const ArithmeticResponse *, lcb_error_t) .

Если вы не хотите реализовывать выделенные обработчики для общих команд, вы можете просто реализовать onDefault (OperationContext *, const ResponseBase *, lcb_error_t) и обрабатывать все команды оттуда.