在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 ++名称,技术来处理 这种情况?
答案 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库改变或添加函数,或者出于任何其他原因。