【问题标题】:How to implement operator-> for an iterator that constructs its values on-demand?如何为按需构造其值的迭代器实现 operator->?
【发布时间】:2015-02-19 09:08:37
【问题描述】:

我有一个类似于容器的 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-&gt;m(其中i 是一个迭代器实例),据我所知,operator-&gt;函数需要返回一个指针。

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

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

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

但是,更新存储的值将需要迭代器检查其当前索引是否小于容器的大小,以便结束后迭代器不会通过访问底层数组的末尾而导致未定义的行为.这会增加(少量)运行时开销——当然,这不足以使解决方案不切实际,但感觉有点不雅。迭代器实际上不需要存储任何东西,只需要一个指向它正在迭代的容器的指针和其中的当前位置。

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

(请注意,我根本需要支持operator-&gt; — 我主要是实现迭代器以便可以使用 C++11 遍历容器“@987654323 @" 循环,而std::tuple 没有任何成员通常希望通过-&gt; 访问。但我仍然想正确地建模迭代器概念;否则感觉就像我在偷工减料。还是我不应该打扰?)

【问题讨论】:

  • operator-&gt; 不必返回指针。它只需要返回可以应用* 的东西。
  • 嗯,所以我可以返回一个“假指针”,它包含一个值并在“取消引用”时返回该值?我不确定这是否比将值存储在迭代器本身中更尴尬。 :-) 但至少(使用内联)运行时开销为零。
  • 无论如何,要建模ForwardIterator,您必须满足以下要求:“如果 a 和 b 都是可解引用的,那么 a == b 当且仅当 *a 和 *b 绑定到同一个对象。”这通常意味着返回代理或包含值的迭代器只能是 InputIterators
  • @KerrekSB,看起来返回值实际上必须支持-&gt;,而不是*。具有自己的operator-&gt;(返回真实指针)的伪指针类可以工作,但在伪指针中仅实现operator*() 是行不通的。
  • [over.ref]/1: "如果 T::operator-&gt;() 存在且运算符为被重载解析机制(13.3)选为最佳匹配函数。”

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


【解决方案1】:

这是一个依赖于 operator-&gt; 重复应用直到返回指针的事实的示例。我们让Iterator::operator-&gt; 将 Contained 对象作为临时对象返回。这会导致编译器重新应用operator-&gt;。然后我们让Contained::operator-&gt; 简单地返回一个指向自身的指针。请注意,如果我们不想将operator-&gt; 放入 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;
    }
}

【讨论】:

  • 当然,要小心:像const int &amp;a = it-&gt;a; 这样的东西通常可以工作,但在这里不行,因为在使用a 时临时对象已经被销毁了。
  • 有什么特别的原因让Contained 直接保存单个值,而不是保存std::tuple(并从operator-&gt; 返回其地址)?
  • @Wyzard:你可以使用元组,但你必须包装它,因为 std::tuple 没有 -> 为它重载。
  • 但是如果元组是Contained 的成员,你可以只返回那个成员变量的地址。 Contained 包装器。 (或者我错过了什么?)
  • @Wyzard:是的,在这种情况下Contained 是包装器,当然你可能会重命名它。
【解决方案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}; }
  // ...
};

这取决于-&gt; 的工作方式。

ptr-&gt;b 的指针 ptr 就是 (*ptr).b

否则定义为(ptr.operator-&gt;())-&gt;b。如果 operator-&gt; 不返回指针,则递归计算。

上面的pseudo_ptr&lt;T&gt; 为您提供了T 副本的包装。

但是,请注意,延长生命周期实际上并不奏效。结果很脆弱。

【讨论】:

    猜你喜欢
    • 2012-02-18
    • 1970-01-01
    • 2010-11-14
    • 2013-08-09
    • 1970-01-01
    • 2021-05-02
    • 1970-01-01
    • 2016-01-23
    相关资源
    最近更新 更多