用枚举类型在C++中封装C-API

3

目前,我正在为一个作为相机驱动程序的C库编写C++包装器。相机可以设置属性,这些属性可以是floatboolint类型。每个属性都有一个ID,该ID是enum PropId的成员。对于这些类型中的每一个,都有一个属性信息struct,一个用于获取和设置值的函数:

GetPropertyAttribsI
GetPropertyAttribsB
GetPropertyAttribsF
PropAttribsI
PropAttribsF
PropAttribsB
SetPropertyValueI
SetPropertyValueF
SetPropertyValueB

我的问题是,我想编写代码来实现以下功能:

  1. 检查要设置的属性值是否在范围内,如果不在范围内,则将其设置为默认值。 范围和默认值可以在 PropAttribs 结构体中查找,该结构体可以使用相应的 GetPropertyAttribs 函数进行初始化。
  2. 使用相应的 SetPropertyValue(I,B,F) 设置属性

我可以查找属性的类型,它在一个 枚举 (PROP_TYPE_INT、PROP_TYPE_FLOAT、PROP_TYPE_BOOL) 中。

因此,我需要一个函数:

checkAndSanitizeProperty(T& value, PropId property)

检查给定属性是否在范围内,否则将其设置为默认值。

骨架应该是以下内容,但我不知道如何使用模板参数使其对boolfloat都可用而不需要复制。设置参数的函数与此非常相似,如果找到任何解决方案,则应具有相同的解决方案。

void CameraHandle::checkAndSanitizeProperty(int& value, VRmPropId property, std::string name) {
  VRmPropInfo propInfo;
  VRM_CHECK(VRmUsbCamGetPropertyInfo(device, property, &propInfo));

  if (VRM_PROP_TYPE_INT != propInfo.m_type) {
    ROS_ERROR("Invalid type of property!");
  }

  VRmPropAttribsI attribs;
  VRmUsbCamGetPropertyAttribsI(device, property, &attribs);
  if (value < attribs.m_min || value > attribs.m_max) {
    ROS_WARN("Invalid value for parameter %s, has to be in [%d,%d], but was: %d",
             name.c_str(),
             attribs.m_min,
             attribs.m_max,
             value);

    ROS_WARN("Default will be used for %s: %d", name.c_str(), attribs.m_default);
    value = attribs.m_default;
  }
}

让我感到烦恼的是我需要重复很多代码,因此我在寻找更简洁的解决方案。我并不主要使用C++,所以对于模板魔法或C++惯用语并没有太多经验。

1个回答

2
宏编程技巧。
#define CONCAT2( A, B ) A##B
#define CONCAT(A,B) CONCAT2(A,B)

#define MAP_STRUCT( X ) \
  template<class T> struct X; \
  template<class T> using CONCAT(X,_t) = typename X<T>::type; \
  template<> struct X<int> { \
    using type= CONCAT(X, I); \
  }; \
  template<> struct X<float> { \
    using type= CONCAT(X, F); \
  }; \
  template<> struct X<bool> { \
    using type= CONCAT(X, B); \
  };

将基本结构名称如 VRmPropAttribs 转换为 VRmPropAttribsI ,使得 VRmPropAttribs_t<int> 等同于 VRmPropAttribsI
#define CALL(X) CONCAT( call_, X )
#define MAP_FUNC_CALL(X) \
  template<class T> \
  struct CALL( X ); \
  template<> struct CALL(X)<int> { \
    template<class...Args> \
    auto operator()(Args&&...args)const \
    -> decltype( CONCAT(X,I)(std::declval<Args>()...) ) { \
      return CONCAT(X,I)(std::forward<Args>(args)...); \
    } \
  }; \

等等,关于B和F的内容。

 #define MAP_FUNC(X) \
    MAP_FUNC_CALL(X) \
    template<class T, class...Args> \
    auto X(Args&&...args) \
    -> typename std::result_of< CALL(X)(Args...) > \
    { return CALL(X)<T>{}(std::forward<Args>(args)...); }

该段代码定义了一个名为X的函数模板,当传入int时,将以相同的参数调用XI

现在,这并不能处理您的%d格式字符串等内容,但您可以使用以下方法:

VRmPropAttribs attribs; VRmUsbCamGetPropertyAttribs(device, property, &attribs); 在使用上述宏之后。一旦您能够执行以上操作,则可以将int替换为T,并使您的方法成为模板方法。

您还需要更好的VRM_PROP_TYPE_INT版本:

template<class T> struct vrm_prop_type{};
template<> struct vrm_prop_type<int>:
  std::integral_constant<int, VRM_PROP_TYPE_INT>
{};

etc可以让您做到以下几点:

vrm_prop_type<T>{} != propInfo.m_type

检查。


那看起来真的很酷。不过,我认为与为每种类型复制函数相比,它也可能更难读和维护。 - jcklie
@reindeer 如果你只有1或2个函数,手动编写宏生成的内容比使用宏更好。这样你最终会得到一个统一的业务逻辑函数,非常不错。 - Yakk - Adam Nevraumont

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接