C++:"Iterable<T>" 接口

C++: "Iterable<T>" interface

我想要实现的目标可能很容易解释:假设我有一个抽象 class,我知道它将包含多个已知类型的对象。然而,容纳这些对象的实际容器将在 sub-classes.
中实现 在我的抽象基础 class 中,我现在想提供一个接口来迭代这些对象。鉴于我不知道(或者更确切地说不想修复)容器的类型,我认为迭代器可能是我最好的选择。

此 class 的概念性声明可能如下所示:

class MyClass {
public:
    // Other interface methods, e.g. size()

    virtual Iterable<MyObject> objects() = 0;
};

这里的目的是我将能够像这样迭代 class 的嵌套对象:

MyClass *class = new ImplementationOfClass();
for (const MyObject &obj : class->objects()) {
    // Do stuff with obj
}

然而,我面临的问题是我似乎无法弄清楚 Iterable<MyObject> 应该如何定义。这个对象的关键属性是在定义这个class的时候我只能指定返回值是可迭代的(使用STL风格的迭代器)并且会产生[=15类型的对象=] 当使用的迭代器被取消引用时。

通常我会为此单独使用一个抽象 class 但这似乎非常棘手(不可能?),因为迭代器总是按值传递,因此据我所知不可能存在多态性。

处理如何将任意迭代器类型作为参数传递给函数的问题总是会得出“使用模板”的答案。但是我认为在我的情况下我不能为此使用模板。不过这个假设可能是错误的,所以请随时纠正我。

本质上,我总是 运行 遇到的障碍是,在某些时候我必须明确地写下迭代器类型,而在我的情况下我不能。我考虑过为此使用模板,但这会抑制适当的多态性(我认为?),因为该抽象接口的用户似乎有显式初始化正确模板的负担。然而,所有这一切的重点是调用者不必关心底层结构。


TL;DR: 有没有办法创建一个接口 class 只承诺是可迭代的并且取消引用迭代器将产生 T 类型的对象?

在@FrançoisAndrieux 的帮助和 的提示下,我找到了解决问题的方法。

本质上,这个想法是创建一个迭代器包装器,它存储一个函数,以便在给定索引的情况下获取给定类型的对象。该索引就是迭代的内容。

这样做的好处是迭代器接口是通过指定取消引用它的对象类型来固定的 return。多态性通过使成员函数 objects() virtual 发挥作用,以便每个子 class 可以构造迭代器本身,提供自定义函数指针。因此,只要有办法将索引映射到容器中的相应元素(无论使用哪个),这个技巧都是可用的。

请注意,您可以直接使用指向例如 std::vector<T>::at 的指针,也可以创建一个将 return 相应元素的自定义函数。

这是迭代器的实现(该实现可能会有所改进,但它似乎完成了工作):

template< typename T > struct iterator_impl {
    using iterator_category = std::forward_iterator_tag;
    using difference_type   = std::ptrdiff_t;
    using value_type        = T;
    using pointer           = T *;
    using reference         = T &;

    using access_function_t = std::function< T &(std::size_t) >;

    // regular Ctor
    iterator_impl(std::size_t start, access_function_t &func, const void *id)
        : m_index(start), m_func(func), m_id(id) {}

    // function-move Ctor
    iterator_impl(std::size_t start, access_function_t &&func, const void *id)
        : m_index(start), m_func(func), m_id(id) {}

    // copy Ctor
    iterator_impl(const iterator_impl &) = default;

    // move ctor
    iterator_impl(iterator_impl &&other) {
        std::swap(m_index, other.m_index);
        m_func = std::move(other.m_func);
        std::swap(m_id, other.m_id);
    }

    // copy-assignment
    iterator_impl &operator=(const iterator_impl &other) = default;

    // prefix-increment
    iterator_impl &operator++() {
        ++m_index;
        return *this;
    }

    // postfix-increment
    iterator_impl operator++(int) {
        iterator_impl old = *this;
        ++(*this);
        return old;
    }

    bool operator==(const iterator_impl &other) { return m_index == other.m_index && m_id == other.m_id; }

    bool operator!=(const iterator_impl &other) { return !(*this == other); }

    T &operator*() { return m_func(m_index); }

    T *operator->() { return &m_func(m_index); };

protected:
    std::size_t m_index = 0;
    access_function_t m_func;
    const void *m_id = nullptr;
};

请注意,我必须引入 m_id 成员变量作为正确比较迭代器的方法(std::function 无法使用 == 进行比较)。它意味着例如包含元素的容器的地址。其唯一目的是确保碰巧具有相同索引但迭代完全不同集合的 2 个迭代器不被视为相等。

在此基础上实现了 Iterable<T>:

template< typename T > struct Iterable {
    using iterator       = iterator_impl< T >;
    using const_iterator = iterator_impl< const std::remove_const_t< T > >;

    Iterable(std::size_t start, std::size_t end, typename iterator_impl< T >::access_function_t &func, const void *id)
        : m_begin(start, func, id), m_end(end, func, id) {}

    iterator begin() { return m_begin; }
    iterator end() { return m_end; }

    const_iterator begin() const { return m_begin; }
    const_iterator end() const { return m_end; }

    const_iterator cbegin() const { return m_begin; }
    const_iterator cend() const { return m_end; }

protected:
    iterator m_begin;
    iterator m_end;
};