【问题标题】:Forward iterator/random acces iterator and `operator*` over temporary iterators前向迭代器/随机访问迭代器和 `operator*` 在临时迭代器上
【发布时间】:2017-09-02 07:11:33
【问题描述】:

这是:

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 的数据/保存。该模型只为您提供存储在模型上的QVariants 的副本。

我的包装器类(operator= 重载的value_type)保存了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 };
    }
};

【问题讨论】:

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


    【解决方案1】:

    隐藏迭代器(即,返回对自身内部事物的引用的迭代器)绝不是有效的前向迭代器。

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

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

    【讨论】:

    • 我是我的情况,同一个迭代器的两个副本,指的是同一个元素(内部QAbstractItemModel项),因为同一个迭代器的两个副本有两个副本QModelIndex它指的是model 中的相同元素。正如我在另一条评论中所说,就我而言,*it == *(my_row_it(it)),但是,&*it != &*(my_row_it(it))。那么,“相当于原版”是什么意思呢?同一个迭代器的两个副本不会返回同一个对象,而是返回同一个对象的代理。
    • 尽管至少在cppreference 文档中,所需的功能都没有提到返回对象的地址,除非您比较,否则无法知道两个对象是否真的相同他们的addressof
    【解决方案2】:

    关于迭代器的一般性声明涵盖了这一点:

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

    §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 的转换(而不是对它的引用)。

    【讨论】:

    • 我的迭代器支持这个。同一个迭代器的两个副本引用同一个项目。我的迭代器没有填充的是 &*it == &*(my_row_it(it))。
    猜你喜欢
    • 2022-11-14
    • 2019-12-06
    • 2020-04-23
    • 1970-01-01
    • 2011-07-09
    • 2012-10-27
    • 2011-05-17
    • 2012-12-13
    • 2015-02-18
    相关资源
    最近更新 更多