迭代器进入移动的容器

时间:2013-10-26 19:39:52

标签: c++ c++11 iterator

我有一个类,其中包含一个容器,以及一个容器中的迭代器。如何正确实现移动构造函数?我似乎记得,通过标准,你不能依赖于移动后剩余有效的迭代器(这是如此愚蠢)。是否有一些方法可以“更新”迭代器,如果它是无效的或什么?或者我是否必须动态分配容器,移动它,然后使迭代器保持有效?

2 个答案:

答案 0 :(得分:3)

更新:使用std::unique_ptr作为容器的持有者是规范的通用解决方案 - 只需移动容器,只需转移所有权并交换迭代器即可。正如您已经说过的那样,您可以将此作为优化,但我希望通用解决方案也非常高效,并且在证明它是真实的之后我只接受代码的更多复杂性(也就是bug潜力)。为您的用例赢得性能。

我将把以下的答案留给未来的读者:阅读它和评论,看看为什么其他解决方案没有真正起作用,以及在哪些情况下它们会造成麻烦。


更新迭代器的显而易见的方法是:

Container c = ...;
Container::iterator it = ...;

const auto d = std::distance( c.begin(), it );
Container n = std::move(c);
it = n.begin();
std::advance( it, d );

通常是线性的,但当迭代器是随机访问迭代器时是常量。

由于您可能不想这样做,因此您有两个选项可以提供帮助:默认构造新容器并使用swap而不使迭代器失效或将容器放入std::unique_ptr并改为移动它。

第一种方法(swap)要求两个实例都有容器实例,这可能比存储在std::unique_ptr中的简单单指针稍大一些。当您经常移动实例时,基于std::unique_ptr的方法似乎比我更好,尽管每次访问都需要一个指针间接。判断(并衡量)适合自己的最佳选择。

答案 1 :(得分:1)

我认为迭代器失效的隐含保证适用于移动ctor。也就是说,以下内容适用于所有容器,但std::array

template<class Container>
struct foo_base
{
    Container c;
    Container::iterator i;

    foo_base(foo_base&& rhs, bool is_end)
    : c( std::move(rhs.c) )
    , i( get_init(is_end, rhs.i) )
    {}

    Container::iterator get_init(bool is_end, Container::iterator ri)
    {
        using std::end; // enable ADL
        return is_end ? end(c) : ri;
    }
};

template<class Container>
struct foo : private foo_base<Container>
{
    foo(foo&& rhs)
    : foo_base(std::move(rhs), rhs.i == end(rhs.c))
    {}
};

通过基类进行复杂的初始化是必要的,因为如果分配器没有为移动分配传播,则不需要移动移动。需要检查迭代器,因为end()迭代器可能无效;必须在移动容器之前执行此检查。但是,如果您可以确保分配器传播(或者移动分配不会使您的情况的迭代器无效),则可以使用下面的更简单版本,将swap替换为移动分配。

N.B。 get_init功能的唯一目的是启用ADL。 foo_base可能有成员函数end,这将禁用ADL。 using-declaration 停止非限定查找以查找可能的成员函数,因此始终执行ADL。你可以使用std::end(c)并摆脱get_init,如果你对在这里输掉ADL感到满意。

如果事实证明移动ctor没有这种隐含的保证,那么swap仍有明确的保证。为此,您可以使用:

template<class Container>
struct foo
{
    Container c;
    Container::iterator i;

    foo(foo&& rhs)
    {
        using std::end; // enable ADL
        bool const is_end = (rhs.i == end(rhs.c));

        c.swap( rhs.c );

        i = get_init(is_end, rhs.i);
    }

    Container::iterator get_init(bool is_end, Container::iterator ri)
    {
        using std::end; // enable ADL
        return is_end ? end(c) : ri;
    }
};

但是,交换有一些要求,在[container.requirements.general] / 7 + 8中定义:

  

调用容器的swap函数的行为是未定义的,除非被交换的对象具有比较相等的分配器或allocator_traits<allocator_type>::propagate_on_container_swap::valuetrue   [...]   属于ComparePred的任何Hashab个对象都应该是可交换的,并且应通过对非成员{{1 }}。如果swapallocator_traits<allocator_type>::propagate_on_container_swap::value,则truea的分配器也应使用对非成员b的无限制调用进行交换。否则,它们不应被交换,并且行为是未定义的,除非   swap

即。两个容器都应该(但不一定)具有相同的分配器。

移动构造OTOH只要求不抛出任何异常(对于分配器感知容器);总是移动分配器。