提升multi_index反向迭代器擦除麻烦

时间:2016-10-14 16:39:56

标签: c++ c++11 boost boost-multi-index

我有以下(简化)代码:

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
namespace bmi = boost::multi_index;

#include <string>
#include <iostream>
#include <cassert>

using Container = boost::multi_index_container<
    std::string,
    bmi::indexed_by< bmi::ordered_non_unique< bmi::identity<std::string> > >
>;

/// Get the base of a non-reverse iterator. It's the iterator itself.
inline
Container::iterator const&
iter_base(Container::iterator const& it)
{
    return it;
}

/** Get a non-reverse iterator that points at the same element as the given reverse_iterator.
 *
 * @param rit reverse_iterator
 * @return a (non-reverse) iterator that points to the same element.
 * @pre @p rit is dereferenceable (not equal to @c rend() of whatever container @p rit came from)
 */
inline
Container::iterator
iter_base(Container::reverse_iterator const& rit)
{
    auto bit = rit.base();
    // if 'rit' is a reverse iterator: &*(rit.base() - 1) == &*rit
    return --bit;
}

template <typename IT>
void evict(Container& c, IT rb, IT fin)
{
    std::vector<std::string> result;
    for (; rb != fin; ) {
        if (rb->size() == 3) {
            auto victim = rb;
            ++rb;
            std::cout << "victim->" << *victim << ", next->" << (rb==fin ? std::string{"THE END"} : *rb) << "\n";
            auto next = c.erase(iter_base(victim));
            std::cout << "size=" << c.size() << "\n";
            for (auto const& s : c) {
                std::cout << "remain: " << s << "\n"; // bar - baz - foo
            }

            rb = IT(next);
            (void)next;
        }
        else {
            result.push_back(*rb);
        }
    }
}

int main(int argc, char**)
{
    bool forward = (argc == 1);

    Container c;
    c.insert("foo"); // will be last
    c.insert("bar");
    c.insert("baz");

    if (forward) {
        auto b = c.lower_bound("baz");

        std::cout << ">> " << *b << "\n"; // prints baz

        auto rb = (b);
        std::cout << "<< " << *rb            << "\n"; // prints baz
        std::cout << "<< " << *iter_base(rb) << "\n"; // prints baz

        evict(c, rb, c.end());
    }
    else {
        auto b = c.upper_bound("baz");

        std::cout << ">> " << *b << "\n"; // prints foo

        auto rb = Container::reverse_iterator(b);
        std::cout << "<< " << *rb            << "\n"; // prints baz
        std::cout << "<< " << *iter_base(rb) << "\n"; // prints baz

        evict(c, rb, c.rend());
    }
}

真正的代码不仅仅是擦除,但这足以说明行为。

EDITED表示循环中不会发生任何删除。 应该以正向或反向顺序将项添加到result,具体取决于使用的迭代器类型。

在没有参数的情况下运行时,forward==true并且输出符合预期:

>> baz
<< baz
<< baz
victim->baz, next->foo
size=2
remain: bar
remain: foo
victim->foo, next->THE END
size=1
remain: bar

使用参数forward==false运行时,输出为:

>> foo
<< baz
<< baz
victim->baz, next->bar
size=2
remain: bar
remain: foo
segmentation fault (core dumped)

(不像预期的那样)

使用地址清理程序进行编译会在第42行(++ rb行)中显示堆后使用。

似乎调用erase(victim)已经以某种方式使rb无效,即使erase不应该使任何其他迭代器无效。

知道我做错了吗?

2 个答案:

答案 0 :(得分:2)

好的,处理反向迭代器是一个痛苦的问题。让我们在执行evict的这部分代码时分析指针业务:

auto victim = rb;
++rb;
auto next = c.erase(iter_base(victim));

在调用evict(c, Container::reverse_iterator(c.upper_bound("baz")), c.rend())时。通过&#34;指向&#34;我的意思是&#34;内部迭代器指向&#34;。我们一步一步地:

  1. 在输入代码之前:rb指向"foo"victim尚不存在。

    auto victim = rb;

  2. rb指向"foo"victim指向"foo"

    ++rb;

  3. rb指向"baz"victim指向"foo"

    auto next = c.erase(iter_base(victim));

  4. "baz"被删除,rb指向已删除 "baz"victim指向"foo"。使用rb进行任何进一步的解除引用,比较或(de / in)创建操作都是未定义的行为。

  5. 我知道您正在尝试编写一个适用于迭代器和反向迭代器的evict函数。一种可行的方法如下:

    template<typename Container>
    std::pair<typename Container::iterator,typename Container::iterator>
    direct_range(
      typename Container::iterator first,
      typename Container::iterator last)
    {
      return {first,last};
    }
    
    template<typename Container>
    std::pair<typename Container::iterator,typename Container::iterator>
    direct_range(
      typename Container::reverse_iterator first,
      typename Container::reverse_iterator last)
    {
      return {last.base(),first.base()};
    }
    
    template <typename IT>
    void evict(Container& c, IT rb, IT fin)
    {
      auto p=direct_range<Container>(rb,fin);
      c.erase(p.first,p.second);
    
      for(auto const& s:c){
        std::cout<<"remain: "<<s<<"\n"; // bar - baz - foo
      }
    }
    

答案 1 :(得分:2)

第二个答案,OP的额外请求是根据迭代器的性质以直接或反向顺序完成遍历。有点小心,这可以这样做: <击>

<击>
template <typename IT>
void evict(Container& c, IT rb, IT fin)
{
    std::vector<std::string> result;
    if(rb != fin) for(;;) {
        IT next = rb;
        ++next;
        bool finished  = (next == fin);
        if (rb->size() == 3) {
            c.erase(iter_base(rb));
            std::cout << "size=" << c.size() << "\n";
            for (auto const& s : c) {
                std::cout << "remain: " << s << "\n"; // bar - baz - foo
            }
        }
        else {
            result.push_back(*rb);
        }
        if(finished) break;
        rb = next;
    }
}

<击> 我的不好,受打击的代码仍在进入UB。请试试这个:

template <typename IT>
void evict(Container& c, IT rb, IT fin)
{
    std::vector<std::string> result;
    if(rb != fin) for(;;) {
        bool finished  = (std::next(rb) == fin);
        if (rb->size() == 3) {
            rb = IT{c.erase(iter_base(rb))};
            std::cout << "size=" << c.size() << "\n";
            for (auto const& s : c) {
                std::cout << "remain: " << s << "\n"; // bar - baz - foo
            }

        }
        else {
            result.push_back(*rb);
        }
        if(finished) break;
    }
}