如何实现operator->对于按需构造其值的迭代器?

时间:2014-12-20 17:58:26

标签: c++ c++11 iterator operator-overloading

我有一个类似容器的C ++类:它有size()operator[]个成员函数。存储的值"在"容器是std::tuple个对象。但是,容器实际上并没有将元组保存在内存中;相反,它根据以不同形式存储的基础数据按需构建它们。

std::tuple<int, int, int>
MyContainer::operator[](std::size_t n) const {
    // Example: draw corresponding elements from parallel arrays
    return { underlying_data_a[n], underlying_data_b[n], underlying_data_c[n] };
}

因此,operator[]的返回类型是临时对象,而不是引用。 (这意味着它不是左值,所以容器是只读的;没关系。)

现在我正在编写一个迭代器类,可以用来遍历这个容器中的元组。我想建模RandomAccessIterator,这取决于InputIterator,但InputIterator需要支持表达式i->m(其中i是迭代器实例),并且我可以说,返回指针需要operator->函数。

当然,我无法返回指向按需构建的临时元组的指针。想到的一种可能性是将一个元组实例作为成员变量放入迭代器中,并使用它来存储迭代器当前所处的任何值的副本:

class Iterator {
private:
    MyContainer *container;
    std::size_t current_index;

    // Copy of (*container)[current_index]
    std::tuple<int, int, int> current_value;
    // ...
};

但是,更新存储的值将要求迭代器检查其当前索引是否小于容器的大小,以便过去的迭代器不会通过访问过去导致未定义的行为底层数组的结尾。这增加了(少量)运行时开销 - 当然不足以使解决方案变得不切实际,但感觉有点不雅。迭代器不应该存储任何东西,除了指向它正在迭代的容器的指针以及其中的当前位置。

对于按需构造其值的迭代器类型,是否有一种干净,完善的方式来支持operator->?其他开发人员将如何做这类事情?

(请注意,我根本不需要 来支持operator-> - 我主要是实现迭代器,以便可以使用C遍历容器++ 11&#34; range for&#34;循环,std::tuple没有任何人通常想要通过->访问的成员。但是我&# 39;尽管如此,我仍然喜欢对迭代器概念进行建模;感觉就像我在偷工减料一样。或者我应该不打扰?)

2 个答案:

答案 0 :(得分:2)

template<class T>
struct pseudo_ptr {
  T t;
  T operator*()&&{return t;}
  T* operator->(){ return &t; }
};

然后

struct bar { int x,y; };
struct bar_iterator:std::iterator< blah, blah >{
  // ...
  pseudo_ptr<bar> operator->() const { return {**this}; }
  // ...
};

这取决于->的工作方式。

指针ptr->b

ptr只是(*ptr).b

否则将其定义为(ptr.operator->())->b。如果operator->没有返回指针,则递归计算。

上面的pseudo_ptr<T>为您提供T

副本的包装

但请注意,终身扩展并不真正起作用。结果很脆弱。

答案 1 :(得分:1)

这里有一个例子,依赖于重复应用operator->直到返回指针的事实。我们让Iterator::operator->将Contained对象作为临时对象返回。这会导致编译器重新应用operator->。然后我们让Contained::operator->只返回一个指向自身的指针。请注意,如果我们不想将operator->放在Contained on-the-fly对象中,我们可以将其包装在一个辅助对象中,该对象返回指向内部Contained对象的指针。

#include <cstddef>
#include <iostream>

class Contained {
    public:
        Contained(int a_, int b_) : a(a_), b(b_) {}
        const Contained *operator->() {
            return this;
        }
        const int a, b;
};

class MyContainer {
    public:
        class Iterator {
                friend class MyContainer;
            public:
                friend bool operator!=(const Iterator &it1, const Iterator &it2) {
                    return it1.current_index != it2.current_index;
                }
            private:
                Iterator(const MyContainer *c, std::size_t ind) : container(c), current_index(ind) {}
            public:
                Iterator &operator++() {
                    ++current_index;
                    return *this;
                }
                // -> is reapplied, since this returns a non-pointer.
                Contained operator->() {
                    return Contained(container->underlying_data_a[current_index], container->underlying_data_b[current_index]);
                }
                Contained operator*() {
                    return Contained(container->underlying_data_a[current_index], container->underlying_data_b[current_index]);
                }
            private:
                const MyContainer *const container;
                std::size_t current_index;
        };
    public:
        MyContainer() {
            for (int i = 0; i < 10; i++) {
                underlying_data_a[i] = underlying_data_b[i] = i;
            }
        }
        Iterator begin() const {
            return Iterator(this, 0);
        }
        Iterator end() const {
            return Iterator(this, 10);
        }
    private:
        int underlying_data_a[10];
        int underlying_data_b[10];
};

int
main() {
    MyContainer c;

    for (const auto &e : c) {
        std::cout << e.a << ", " << e.b << std::endl;
    }
}