C++:“Iterable<T>”接口

问题描述

我想要实现的目标可能很容易解释:考虑我有一个抽象类,我知道它将包含多个已知类型的对象。然而,包含这些对象的实际容器将在子类中实现。
在我的抽象基类中,我现在想提供一个接口来迭代这些对象。鉴于我不知道(或者不想修复)容器的类型,我认为迭代器可能是我最好的选择。

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

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

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

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

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

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

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

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

本质上,我总是遇到的障碍是在某些时候我必须明确写下迭代器类型,而在我的情况下我不能。我考虑过为此使用模板,但这会抑制正确的多态性(我认为?),因为该抽象界面的用户似乎有责任明确初始化正确的模板。然而,所有这一切的全部意义在于调用者不必关心底层结构。


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

解决方法

在@FrançoisAndrieux 的帮助和 https://stackoverflow.com/a/4247445/3907364 的提示下,我找到了解决问题的方法。

本质上的想法是创建一个迭代器包装器,它存储一个函数,如果给定索引,则获取给定类型的对象。那个索引然后被迭代。

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

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

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

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,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,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;
};