在迭代迭代器上转发迭代器/随机访问迭代器和`operator *`

时间:2017-04-07 03:12:52

标签: c++ iterator random-access temporary-objects

这是:

auto& ref1 = *it++;
ref1 = expression; // (1)

前向迭代器所需的语义之一?那么随机访问迭代器呢?

auto& ref1 = it[3];
ref1 = expression; // (2)

根据cppreference,前向迭代器需要:

// Return reference, which equals to (const) value_type &
*it++ === value_type&

和随机访问迭代器:

it[n] === *(it + n)

这是相同的情况,这意味着在两种情况下你都是解除引用临时(迭代器)。在我的例子中,我的迭代器通过复制存储一个索引,该索引允许访问一个容器,该容器不能通过索引直接访问存储的元素。

工作正常:

*it++ = value;

因为it的临时副本有句子范围。

但在这种情况下:

type& val = *it++;
val = 3;

我们得到了未定义的行为,因为副本已在第二行中被销毁。

在我的情况下,我有一个QModelIndex包装器来从/ QAbstractItemModel获取数据/保存。该模型仅为您提供模型上存储的QVariant的副本。

我的包装类(value_type重载operator=)保存了QModelIndex的实例(用于操作模型),迭代器包含了该包装器的实例。所以,如果迭代器被破坏,包装器和索引也是如此。

我认为只要行(1)(2)不需要支持就可以解决这两个问题。

注意:我的实现或多或少(简化):

// The value type
struct index
{
    QModelIndex qidx;

    index& operator=(QVariant const& val)
    {
        if (qidx.isValid())
            qidx.model()->setData(qidx, val);

        return *this;
    }
};

// Private class actually. The "movements" cannot be done
// over the value type because it will cause, in functions
// returning references to the value type, to increase the chaos.
// So, I make the index points to different model items using
// this class.
struct index_manipulator
{
    QModelIndex& qidx;

    void move(int rows, int cols)
    {
        if (qidx.isValid())
            qidx = qidx.model()->index(qidx.row() + rows,
                                       qidx.column() + cols);
    }
};

struct index_safe_ref
{
    mutable index idx;
    operator index&() const { return idx; }
};

struct my_row_it
{
    index idx;
    index_manipulator manip = {idx.qidx};

    my_row_it(QAbstractItemModel* m, int col)
        : idx(m ? m->index(0, col) : QModelIndex())
    {}

    index& operator*() const { return idx; }

    my_row_it operator++(int) const
    {
        auto copy = it;
        manip.move(1, 0);
        return copy;
    }

    index_safe_ref my_row_it::operator[](difference_type n) const
    {
       auto it = it + n; // Operator+ is over there.
       return { it.idx };
    }
};

2 个答案:

答案 0 :(得分:1)

存储迭代器(即,返回对其内部内容的引用的迭代器)永远不是有效的前向迭代器。

一般的迭代器必须是CopyConstructible[iterator.iterators]/2.1,除其他外,它要求迭代器的副本等同于原始的迭代器。它遵循前向迭代器及其副本必须比较相等,[forward.iterators]/6要求两个相等的可解除引用的迭代器ab*a*b必须绑定到同一个对象,而不能对于存储迭代器感到满意。

如果你需要忽略一个要求,我建议忽略一个说reference必须是实际引用类型的那个,把你的存储迭代器变成一个代理迭代器。在标准库(vector<bool>::iterator)中已经建立了这种做法,任何破坏都可能是一个响亮的编译时错误,而不是无声的运行时恶作剧。

答案 1 :(得分:0)

这是关于迭代器的一般声明:

  

破坏迭代器可能会使先前从该迭代器获得的指针和引用无效。

     

§24.2.1/ 9 N3337

但是,正如T.C.如果返回对迭代器对象中包含的对象的引用,则other answer指出你的迭代器不能是有效的前向迭代器(或者更严格的东西)。

我看到两个解决方案:按值返回index对象,或者返回对堆已分配index对象的引用。

注意,输入迭代器需要支持:

value_type temp = *iterator++; // or *iterator; ++iterator;
// use temp

所以在你的情况下,这必须有效(但应该尽我所能):

index temp = *iterator++;
temp = expression.

这与行(1)不同,因为上面的代码涉及到value_type的转换(而不是对它的引用)。