将范围传递给 C++ 时,如何处理 iterator/const_iterator 不匹配问题?算法?

发布于 2025-01-01 12:01:17 字数 1130 浏览 2 评论 0原文

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

#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 函数有一个非-常量事件列表;此列表操作系统传递给 eatEvents 方法,该方法采用 const 引用并生成 const_iterator。现在 compressAndCopyEatenEvenst 函数想要复制吃掉事件的范围,因此它决定使用某种算法(这里是 std::copy ,这当然也可以)被替换为正确的 std::list 构造函数调用 - 重点是这个问题存在于所有类型的范围中)。

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

这里是否存在设计缺陷导致了这种混乱?你会如何应对?

A part of the source code for a project I'm working on, which is responsible for compressing a sequence of 'events', looks like this:

#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
}

The issue here is that the compressAndCopyEatenEvents function has a non-const list of events; this list os passed to the eatEvents methods, which takes a reference-to-const and yields a const_iterator. Now the compressAndCopyEatenEvenst function would like to copy the range of eaten events away, so it decides to use some algorithm (std::copy here, which of course could just as well be replaced with the right std::list constructor call - the point is that this problem exists with all kinds of ranges).

Unfortunately(?) many (if not all?) ranges need to be composed from the same iterator type. However, in the above code, 'e.begin()' yields an EventList::iterator (because the object is not const) but 'newEnd' is an EventList::const_iterator.

Is there a design weakness here which causes this mess? How would you tackle it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

梦开始←不甜 2025-01-08 12:01:17

在 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_iterator),因此您可以这样做简单地

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

In C++03 the only possible way is to cast. (which is ugly, which is a design flaw, yes).

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

or have another named variable:

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

In C++11 you have cbegin and cend functions (that always return const_iterators) so you'd do simply

std::copy( e.cbegin(), newEnd, std::back_inserter( eatenEvents ) );
白日梦 2025-01-08 12:01:17

考虑使用

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

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

Consider using

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

This will cause the correct list::begin() overload to be called, and for std::copy to compile cleanly.

辞取 2025-01-08 12:01:17

看看大师怎么说:

Scot Meyers in effective STL

第 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_iterator 转换为迭代器。

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)

See what the master says:

Scot Meyers in Effective STL

Item 26. Prefer iterator to const iterator, reverse_iterator, and
const_reverse_iterator.
Though containers support four iterator types, one of those types has privileges the others do not have. That type is iterator, iterator is special.
the conversions that exist among iterator types.

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

Item 27. Use distance and advance to convert a container's const_iterators to 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

What works is advance and distance

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)
意中人 2025-01-08 12:01:17

您可以添加 eatEvents 的第二个重载,这样编译器将自动选择正确的重载,以保留常量性:(

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

其中一个或两个可以是非虚拟的,并以单个底层函数的形式实现。)

有时,效果很好,尽管我不相信这是完美的。

You could add a second overload of eatEvents, so the compiler will automatically pick the right one so as to preserves const-ness:

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

(One or both of them could be non-virtual and implemented in terms of a single underlying function.)

Sometimes that works well, although I'm not convinced it's the perfect thing here.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文