如何编写一个尊重constness的C struct指针包装器

时间:2014-11-26 15:19:22

标签: c++ c

我正在使用C ++但使用一些C API,它们通过将指针传递给结构体来工作。为了在C ++代码中使用这些结构中的数据,我正在为每个结构编写一个C ++类。例如,如果我有像

这样的C结构
struct cStruct {
  int32_t data;
};

然后我用以下内容包装它:

class CppWrapper {
  public:
    CppWrapper(cStruct* structData) : m_structData(structData) {}

    // These are the interface the user will deal with
    int data() const {return m_structData->data;}
    void setData(int data) {m_structData->data = data;}

  private:
    cStruct* m_structData = nullptr;
};

可以像

一样轻松使用
cStruct* s = some_c_function();
CppWrapper w(s);
w.setData(5);

但是,如果我有一个返回const cStruct*的C函数,那么我就不能使用CppWrapper并且必须创建一个单独的包装器,它将const cStruct*作为构造函数参数,存储const cStruct*并且不包含setData()等编辑功能。

有没有办法一般编写一个能够适应const或非const指针并保持该constness的包装器?

3 个答案:

答案 0 :(得分:0)

初始解决方案

我最终得到的解决方案是在结构类型上模板化包装器,这样我就可以拥有一个只与const指针一起使用的模板实例它看起来像

template<typename StructType = cStruct>
class Wrapper
{
  public:
    Wrapper(StructType* structData) : m_structData(structData) {}

    // These are the interface the user will deal with
    int data() const {return m_structData->data;}
    void setData(int data) {m_structData->data = data;}

  private:
    StructType* m_structData = nullptr;
};

我给出了模板类型的默认参数,结构类型既是一种文档形式,也是在某些常见情况下减少类型化。

只需执行

即可在代码中使用
cStruct* myCArray = some_c_function();
auto wrapper = WrapperType<>(myCArray)

const cStruct* myConstCArray = some_const_c_function();
auto constWrapper = WrapperType<const cStruct>(myConstCArray);

为了进一步提供帮助,我定义了一个定义为

make_wrapper()函数
template <template <typename> class WrapperType, typename StructType>
WrapperType<StructType> make_wrapper(StructType* structData)
{
  return WrapperType<StructType>(structData);
}

这样你就可以了

cStruct* myCArray = some_c_function();
auto wrapper = make_wrapper(myCArray)

const cStruct* myConstCArray = some_const_c_function();
auto constWrapper = make_wrapper(myConstCArray);

因此make_wrapper()成为通用界面。

基于此,可以使用Wrapper接口

wrapper.setData(42);
std::cout << wrapper.data() << std::endl;

并致电

constWrapper.setData(42);

给出了类似assignment of member ‘cStruct::data’ in read-only object的恰当错误。

允许转换为const

为了允许Wrapper<cStruct>转换为Wrapper<const cStruct>(但不是相反),有必要引入一组构造函数和operator=重载。这些构造函数只能在类的const版本中使用。

使用std::enable_if以及std::is_conststd::remove_const时,这是可行的。它不是最好的,因为它需要构造函数的伪参数,但它完成了工作。

这些声明必须放在Wrapper的类定义中。

template <typename OtherT,
          typename = typename std::enable_if<std::is_same<const OtherT, StructType>::value>::type>
Wrapper(const Wrapper<OtherT>& nonConstOther)
  : m_structData(nonConstOther.m_structData) {}

template <typename Dummy = StructType,
          typename std::enable_if<std::is_const<Dummy>::value>::type>
Wrapper<StructType> operator=(const Wrapper<typename std::remove_const<StructType>::type>& nonConstOther)
  { m_structData = nonConstOther.m_structData; }
friend class Wrapper<typename std::add_const<StructType>::type>;

感谢Jonathan Wakely使用std::is_same获得更好构造函数的提示。

可重复使用的界面

在C ++界面中需要不止一个以便与许多其他C结构进行交互是很常见的。因此,我认为将公共代码(特别是模板重量级的东西)分解出来以便减少样板文件是有用的。

我最终创建了一个基类observer_ptr_wrapper(命名为类似于the proposed observer_ptr),它保留了内部:

template<typename PointerType>
class observer_ptr_wrapper
{
public:
  observer_ptr_wrapper(PointerType* structData) : m_structData(structData) {}

  using NonConstPointerType = typename std::remove_const<PointerType>::type;
  friend class observer_ptr_wrapper<typename std::add_const<PointerType>::type>;

  template <typename OtherT,
            typename = typename std::enable_if<std::is_same<const OtherT, PointerType>::value>::type>
  observer_ptr_wrapper(const observer_ptr_wrapper<OtherT>& nonConstOther)
    : m_structData(nonConstOther.m_structData) {}

  template <typename Dummy = PointerType,
            typename std::enable_if<std::is_const<Dummy>::value>::type>
  observer_ptr_wrapper<PointerType> operator=(const observer_ptr_wrapper<typename std::remove_const<PointerType>::type>& nonConstOther)
    {m_structData = nonConstOther.m_structData;}

protected:
  PointerType* m_structData = nullptr;
};

然后可以继承:

template <typename StructType = const cStruct>
class cStructWrapper : public observer_ptr_wrapper<StructType>
{
public: 
  // These are the interface the user will deal with
  int data() const {return m_structData->data;}
  void setData(int data) {m_structData->data = data;}

//This is the boilerplate
public: using observer_ptr_wrapper<StructType>::observer_ptr_wrapper;
private: using observer_ptr_wrapper<StructType>::m_structData;
};

这样整个样板文件就减少到这两行。

答案 1 :(得分:0)

你可以创建多个构造函数:

class CppWrapper {
  public:
    CppWrapper(cStruct* structData) : m_structData(structData), mc_structData(nullptr) {}
    CppWrapper(const cStruct* structData_c) : mc_structData(structData) {}
    // These are the interface the user will deal with
    int data() const {return m_structData->data;}
    void setData(int data) {m_structData->data = data;}

  private:
    cStruct* m_structData = nullptr;
    const cStruct* mc_structData;

};

答案 2 :(得分:0)

从简单的事情开始怎么样?模板专业化和继承的组合对于提供您想要的东西有很长的路要走。

#include <iostream>

struct cStruct {
  int32_t data;
};

template <typename T> class CppWrapper;

// const version implements const member functions (getters)
template <>
class CppWrapper<const cStruct *> {
 public:
  CppWrapper(const cStruct * d) : d(d) {}

  int data() const { return d->data; }

 private:
  const cStruct * d;
};

// non-const version implements non-const member functions (setters)
template <>
class CppWrapper<cStruct *> : public CppWrapper<const cStruct *> {
 public:
  CppWrapper(cStruct * d) : CppWrapper<const cStruct *>(d), d(d) {}

  void setData(int data) { d->data = data; }

 private:
  cStruct * d;
};

int main(int argc, char * argv[]) {
  struct cStruct s;
  s.data = 0;

  CppWrapper<const cStruct *> a{&s};
  CppWrapper<cStruct *> b{&s};

  const CppWrapper<const cStruct *> c{&s};
  const CppWrapper<cStruct *> d{&s};

  // Getters...
  std::cout << "a.data() = " << c.data() << std::endl;
  std::cout << "b.data() = " << d.data() << std::endl;
  std::cout << "c.data() = " << c.data() << std::endl;
  std::cout << "d.data() = " << d.data() << std::endl;

  // Setters...
  std::cout << "Setting data to 1" << std::endl;
  // a.setData(1);  // Won't compile, as you would hope
  b.setData(1);
  // c.setData(1);  // Won't compile, as you would hope
  // d.setData(1);  // Won't compile, as you would hope

  // Getters (again)
  std::cout << "a.data() = " << c.data() << std::endl;
  std::cout << "b.data() = " << d.data() << std::endl;
  std::cout << "c.data() = " << c.data() << std::endl;
  std::cout << "d.data() = " << d.data() << std::endl;

  return 0;
}

非const版本继承了所有const版本的'getter'函数,并使用'setter'扩展它。没有那么多的样板代码;可能还不足以保证更通用的解决方案?