【问题标题】:How do you handle iterator/const_iterator mismatches when passing ranges to C++ algorithms?在将范围传递给 C++ 算法时,如何处理迭代器/常量迭代器不匹配?
【发布时间】:2012-02-01 11:02:09
【问题描述】:

我正在做的一个项目的部分源代码,负责压缩一系列“事件”,如下所示:

#include <iterator>
#include <list>

typedef int Event;

typedef std::list<Event> EventList;

struct Compressor {
    // Returns an iterator behind the last element which was 'eaten'
    virtual EventList::const_iterator eatEvents( const EventList &l ) = 0;
};

// Plenty of Compressor subclasses exist

void compressAndCopyEatenEvents( Compressor &c ) {
    EventList e;
    e.push_back( 1 );
    EventList::const_iterator newEnd = c.eatEvents( e );

    EventList eatenEvents;
    std::copy( e.begin(), newEnd, std::back_inserter( eatenEvents ) ); // barfs
}

这里的问题是compressAndCopyEatenEvents 函数有一个非常量的事件列表;此列表 os 传递给 eatEvents 方法,该方法采用对 const 的引用并产生 const_iterator。现在compressAndCopyEatenEvenst 函数想复制吃掉事件的范围,所以它决定使用一些算法(这里是std::copy,当然也可以用正确的std::list 构造函数调用来替换——重点是各种范围都存在这个问题)。

不幸的是(?)许多(如果不是全部?)范围需要由相同的迭代器类型组成。然而,在上面的代码中,'e.begin()' 产生一个 EventList::iterator(因为对象不是 const),而 'newEnd' 是一个 EventList::const_iterator

这里是否存在导致这种混乱的设计缺陷?你会怎么处理呢?

【问题讨论】:

  • 你需要非常量迭代器吗?如果没有,那么你应该让你的所有代码只使用 const 迭代器。
  • 我猜EventList::const_iterator begin = e.begin(); 传递它太多了?由于您的使用方法,我看不到任何方法...

标签: c++ templates iterator


【解决方案1】:

在 C++03 中,唯一可能的方法是强制转换。 (这是丑陋的,这是一个设计缺陷,是的)。

std::copy( static_cast<EventList::const_iterator>e.begin(), newEnd, std::back_inserter( eatenEvents ) );

或者有另一个命名变量:

EventList::const_iterator constBegin = e.begin();
std::copy(constBegin , newEnd, std::back_inserter( eatenEvents ) );

在 C++11 中,你有 cbegincend 函数(它们总是返回 const_iterators)所以你会做的很简单

std::copy( e.cbegin(), newEnd, std::back_inserter( eatenEvents ) );

【讨论】:

  • 您可以将e 分配给const EventList&amp; 对象,这将自动生成所需的const_iterator
【解决方案2】:

考虑使用

EventList::const_iterator b = e.begin();
std::copy( b, newEnd, std::back_inserter( eatenEvents ) );

这将导致正确的list::begin() 重载被调用,并且std::copy 可以干净地编译。

【讨论】:

    【解决方案3】:

    看看大师怎么说:

    Scot Meyers 的有效 STL

    Item 26. 使用迭代器优于 const 迭代器、reverse_iterator 和 const_reverse_iterator。 尽管容器支持四种迭代器类型,但其中一种类型具有其他类型所没有的特权。那个类型是迭代器,迭代器是特殊的。

    typedef deque<int> IntDeque; //STL container and
    typedef lntDeque::iterator Iter; // iterator types are easier
    typedef lntDeque::const_iterator ConstIter; // to work with if you
    // use some typedefs
    Iter i;
    ConstIter ci;
    … //make i and ci point into
    // the same container
    if (i == ci ) ... //compare an iterator
    // and a const_iterator
    

    第 27 项。使用 distance 和 Advance 将容器的 const_iterators 转换为迭代器。

    typedef deque<int> IntDeque; //convenience typedefs
    typedef lntDeque::iterator Iter;
    typedef lntDeque::const_iterator ConstIter;
    ConstIter ci; // ci is a const_iterator
    …
    Iter i(ci); // error! no implicit conversion from
    // const_iterator to iterator
    Iter i(const_cast<Iter>(ci)); // still an error! can't cast a
    // const_iterator to an iterator
    

    有效的是前进和距离

    typedef deque<int> IntDeque; //as before
    typedef IntDeque::iterator Iter;
    typedef IntDeque::const_iterator ConstIter;
    IntDeque d;
    ConstIter ci;
    … // make ci point into d
    Iter i(d.begin()); // initialize i to d.begin()
    Advance(i, distance(i, ci)) //move i up to where ci is
    // (but see below for why this must
    // be tweaked before it will compile)
    

    【讨论】:

    • +1 用于提及 Effective STL - 我在家里有这个,但我没想过咨询它。我会阅读这个!
    • 总是喜欢STL!!因为算法比手写循环更有效!!
    【解决方案4】:

    您可以添加第二个 eatEvents 重载,因此编译器会自动选择正确的重载以保留 const-ness:

    virtual EventList::iterator eatEvents( EventList &l ) = 0;
    

    (其中一个或两个都可以是非虚拟的,并根据单个底层功能实现。)

    有时效果很好,虽然我不相信这是完美的东西。

    【讨论】:

      猜你喜欢
      • 2014-10-14
      • 1970-01-01
      • 1970-01-01
      • 2015-04-15
      • 1970-01-01
      • 1970-01-01
      • 2012-06-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多