为C ++消费包装C API的策略

时间:2016-03-23 19:01:11

标签: c++ c c++11 design-patterns

在C ++应用程序中,我必须与C库接口。该库有几个组件,每个组件都有自己的API。 API非常相似,只是在函数的前缀和某些处理程序类型中有所不同。 E.g:

// bull component
void bull_create(bull_handle_t** handle, error_details_t* error_details);
void bull_destroy(bull_handle_t* handle, error_details_t* error_details);
void bull_get_data(bull_handle_t* handle, uint8_t* buffer, error_details_t* error_details);

// frog component
void frog_create(frog_handle_t** handle, error_details_t* error_details);
void frog_destroy(frog_handle_t* handle, error_details_t* error_details);
void frog_get_data(frog_handle_t* handle, uint8_t* buffer, error_details_t* error_details);

// bullfrog component
void bullfrog_create(bullfrog_handle_t** handle, error_details_t* error_details);
void bullfrog_destroy(bullfrog_handle_t* handle, error_details_t* error_details);
void bullfrog_get_data(bullfrog_handle_t* handle, uint8_t* buffer, error_details_t* error_details);

// NOTE: components can and will be added at any time following the same pattern.

我希望以最少的代码重复以通用方式将其包装为C ++消费。 首先是天真的方法:

class bull_backend
{
public:
   ...
   void get_data (uint8_t* buffer)
   {
       error_details_t err;
       bull_get_data(handle, buffer, &err);
   }
   ...

private:
    bull_handle_t* handle;
};

class frog_backend
{
public:
   ...
   void get_data (uint8_t* buffer)
   {
       error_details_t err;
       frog_get_data(handle, buffer, &err);
   }
   ...

private:
    frog_handle_t* handle;
};

class bullfrog_backend
{
public:
   ...
   void get_data (uint8_t* buffer)
   {
       error_details_t err;
       bullfrog_get_data(handle, buffer, &err);
   }
   ...

private:
    bullfrog_handle_t* handle;
};

这并没有完全削减它。我将只是复制C API,但现在以类的形式。 唯一不同的是标识C实体的前缀。

我心中的下一件事就是准备一个宏,例如。

#define GENERATE_BACKEND(...)
    ...

只会替换前缀。但我不喜欢那样。感觉不像 现代C ++。

我能做的另一件事就是将所有内容分组到一个类模板中 做标签调度或使用enable_if,例如

template <typename Component>
class backend
{
public:

    ...
    template<typename = typename std::enable_if<std::is_same<Component, bull_component>::value>::type>
    void get_data (uint8_t* buffer)
    {
       error_details_t err;
       bull_get_data(handle, buffer, &err);
    }

    template<typename = typename std::enable_if<std::is_same<Component, frog_component>::value>::type>
    void get_data (uint8_t* buffer)
    {
       error_details_t err;
       frog_get_data(handle, buffer, &err);
    }

    template<typename = typename std::enable_if<std::is_same<Component, bullfrog_component>::value>::type>
    void get_data (uint8_t* buffer)
    {
       error_details_t err;
       bullfrog_get_data(handle, buffer, &err);
    }
    ...

private:
    typename Component::handle_type handle;
};

struct frog_component
{
   using handle_type = frog_handle_t*;
};

struct bull_component
{
   using handle_type = bull_handle_t*;
};

struct bullfrog_component
{
   using handle_type = bullfrog_handle_t*;
};

using frog_backend = backend<frog_component>;
using bull_backend = backend<bull_component>;
using bullfrog_backend = backend<bullfrog_component>;

仍然感觉不对劲。除了明显的代码之外,另一件困扰我的事情 重复的是,除了宏版本:),它们不能很好地扩展 添加C组件。

我总是觉得必须有更好的东西。 那么,有没有人知道一个更好,更有价值的现代C ++名称,技术来处理 这种情况?

3 个答案:

答案 0 :(得分:3)

我想到的是

template <typename H> using create_t = void (*)(H**, error_details_t*);
template <typename H> using destroy_t = void (*)(H*, error_details_t*);
template <typename H> using get_data_t = void (*)(H*, uint8_t* buffer, error_details_t*);

template <typename H,
          create_t<H> create,
          destroy_t<H> destroy,
          get_data_t<H> get_data>
class backend
{
    backend() { error_details_t err; create(&handle, err);}
    ~backend() {  error_details_t err; destroy(handle, err);}

    backend(const backend&) = delete;
    backend& operator =(const backend&) = delete;
    backend(backend&&) = delete;
    backend& operator =(backend&&) = delete;

    void get_data(uint8_t* buffer)
    {
         error_details_t err;
         get_data(handle, buffer, &err);
    }

private:
   H* handle = nullptr;
};

using bull = backend<bull_handle_t, &bull_create, &bull_destroy, &bull_get_data>;
using frog = backend<frog_handle_t, &frog_create, &frog_destroy, &frog_get_data>;
using bullfrog = backend<bullfrog_handle_t,
                         &bullfrog_create,
                         &bullfrog_destroy,
                         &bull_get_data>;

也许error_details_t可以成为会员, 你也应该处理错误。

答案 1 :(得分:1)

我在想@ Jarod42暗示着什么

template <typename T>
class component
{
private:
    typedef void (*destroyMethod)(T*,error_details_t*);
    ...
public:
    component(destroyMethod,...)
    {
        destroy = destroyMethod;
        ...
    }
    void Destroy()
    {
        error_details_t err;
        destroy(handle, err);
    }
    ...
private:
    destroyMethod destroy;
    T * handle;
};

然后实现类似:

component<bull_handle_t> yourBull(bull_destroy, ...)

你可以用一些额外的工作来包掉最后一部分

答案 2 :(得分:0)

您有一组按部门排列的C函数,其名称和签名遵循一致的模式。如果希望C ++包装器调用其中一个函数,则函数的名称必须以这种或那种方式出现在C ++代码中。您实现这一目标的选择是将名称字面插入包装器代码中的适当位置,或者通过宏系统地生成它们。你已经拒绝了两种选择。

如果你坚持避免使用宏,你可以考虑编写一个代码生成器来输出所有类定义的C ++源代码(从字面上调用所需的函数)。也许这比手工编写所有内容更令人满意,并且它可以让你轻松地重新生成类,如果它们有缺陷,如果C库改变或添加函数,或者出于任何其他原因。