用写迭代器支持在 C++ 中包装遗留 C API

Wrapping a legacy C API in C++ with write iterator support

我有一个遗留的 C API 到类似容器的对象(具体来说,Python C API 到元组)我想用一个漂亮的 C+ 包装+14 API,这样我就可以使用迭代器了。我应该如何实施它?

这里有更多详细信息。我们有以下无法更改的现有 C API:

Py_ssize_t PyTuple_GET_SIZE(PyObject *p);
PyObject* PyTuple_GET_ITEM(PyObject *p, Py_ssize_t pos);
void PyTuple_SET_ITEM(PyObject *p, Py_ssize_t pos, PyObject *o)

我们想创建一个 class,它允许您访问元组中元素的 read/write 迭代器。

前向只读迭代器定义起来并不难。这是我拥有的:

class PyTuple {
private:
  PyObject* tuple;

public:
  PyTuple(PyObject* tuple) : tuple(tuple) {}

  class iterator {
    // iterator traits
    PyObject* tuple;
    Py_ssize_t index;
  public:
    iterator(PyObject *tuple, Py_ssize_t index) : tuple(tuple), index(index) {}
    iterator& operator++() { index++; return *this; }
    iterator operator++(int) { auto r = *this; ++(*this); return r; }
    bool operator==(iterator other) const { return tuple == other.tuple && index == other.index; }
    bool operator!=(iterator other) const { return !(*this == other); }
    PyObject* operator*() { return PyTuple_GET_ITEM(tuple, index); }
    // iterator traits
    using difference_type = Py_ssize_t;
    using value_type = PyObject*;
    using pointer = PyObject**;
    using reference = PyObject*&;
    using iterator_category = std::forward_iterator_tag;
  };

  iterator begin() {
    return iterator(tuple, 0);
  }

  iterator end() {
    return iterator(tuple, PyTuple_GET_SIZE(tuple));
  }
}

但是,我不太确定如何支持写入。我必须以某种方式让 *it = pyobj_ptr 工作。按照惯例,这将通过将类型更改为 PyObject*& operator*() 来完成(以便它给出一个左值),但我不能这样做,因为元组 "write" 需要经过 PyTuple_SET_ITEM。我听说您可以使用 operator= 来解决这种情况,但我不确定是否应该使用通用引用 (Why no emplacement iterators in C++11 or C++14?) or a proxy class (What is Proxy Class in C++),并且我不确定代码应该是什么样子。

您要找的基本上是代理参考。您不想取消引用到 PyObject*,而是想取消引用到本身可以给您 PyObject* 的东西。这类似于 vector<bool> 等类型的迭代器的行为方式。

基本上,你希望 operator*() 给你这样的东西:

class PyObjectProxy {
public:
    // constructors, etc.

    // read access
    operator PyObject*() const { return PyTuple_GET_ITEM(tuple, index); }

    // write access
    void operator=(PyObject* o) {
        PyTuple_SET_ITEM(tuple, index, o); // I'm guessing here
    }
private:
    PyObject* tuple;
    Py_ssize_t index;  
};