C++17 pimpl 习语上下文中的自定义迭代器



我想知道如何在不公开实现的情况下让 c++ 类可迭代(stl 兼容)?

项目的结构是这样的:

Stream

class Stream
{
public:
Stream();
[...]
StreamIterator iter()
{
return StreamIterator(this);
}
private:
class impl;
std::unique_ptr<impl> pimpl;
};

StreamFilter

class StreamFilter
{
public:
StreamFilter();
[...]
private:
class impl;
std::shared_ptr<impl> pimpl;
};

StreamIterator

class StreamIterator
{
public:
StreamIterator(Stream* streamToFilter);
[...]
void addFilter(StreamFilter* filter);
void removeFilter(StreamFilter* filter);
[...]
private:
class impl;
std::unique_ptr<impl> pimpl;
};

StreamFilter是不同筛选策略的基类。

为了示例代码中的简单起见,我使用了原始内存指针,当然在实际的开发代码中,我使用了智能指针:共享,弱...

我想让StreamIterator以 STL 方式变得可迭代,执行以下操作:

StreamIterator iter = stream.iter();
iter.addFilter(new FilterByOffset([...with parameters...]));
for (auto item : iter)
{
[...doing something with filtered items...]
}

基本方法是添加一些访问器以允许基于范围的 for 循环。

StreamIterator

class StreamIterator
{
public:
StreamIterator(Stream* streamToFilter);
[...]
iterator begin();
iterator end();
const_iterator cbegin() const;
const_iterator cend() const;
[...]
void addFilter(StreamFilter* filter);
void removeFilter(StreamFilter* filter);
[...]
private:
class impl;
std::unique_ptr<impl> pimpl;
};

其中iteratorconst_iterator基本上是内部容器迭代器的typedef。这就是问题所在。

首先,我不想在StreamIterator标头中公开私有实现。因此,这里不允许iteratorconst_iterator

其次,由于流过滤策略,返回的迭代器不仅仅是某些内部 stl 容器的别名。在内部实现中,我需要以函子方式调用过滤器来检查是否需要排除该项。

StreamIterator标头中唯一允许的类型是返回的项对象的类型。

有没有办法做到这一点?

谢谢!

附加信息

也许这个声明是允许私有实现的一种方式,我需要调查更多:

StreamIterator

class StreamIterator
{
public:
StreamIterator(Stream* streamToFilter);
[...]
struct iterator
{
Object::Ptr operator*();
iterator& operator++();
bool operator!= (const iterator& it) const;
};
typedef typename StreamIterator::iterator iterator;
iterator begin();
iterator end();
[...]
void addFilter(StreamFilter* filter);
void removeFilter(StreamFilter* filter);
[...]
private:
class impl;
std::unique_ptr<impl> pimpl;
};

首先,不要称它为StreamIterator; 迭代器是一个类似指针的对象,其中 2 个可以指定一个范围。 你的StreamIterator没有这个。 在这里重用定义良好的迭代器术语不会有任何好处。


现在,您的StreamIterator是某种迭代器的范围。 所以我们称之为StreamRange.

为了隐藏StreamRange的迭代方式,您必须隐藏它使用的迭代器的工作方式。

而这 - 隐藏流迭代器的实现 - 有相当大的成本。

循环迭代时,循环中的每个步骤都涉及++*以及==。 在每一个上抛出指针间接寻址和 vtable 查找(或等效项)会使循环速度变慢。

但这是如何做到的。

template<class Value>
struct any_input_iterator {
using difference_type = std::ptrdiff_t;
using value_type=Value;
using pointer = value_type*;
using reference = value_type;
using iterator_category = std::input_iterator_tag;
private:
struct vtable_t {
bool(*equal)( std::any const& lhs, std::any const& rhs ) = 0;
Value(*get)( std::any& ) = 0;
void(*inc)( std::any& ) = 0;
};
vtable_t const* vtable = 0;
std::any state;
template<class U>
static vtable_t make_vtable() {
return {
[](std::any const& lhs, std::any const& rhs)->bool {
return std::any_cast<U const&>(lhs) == std::any_cast<U const&>(rhs);
},
[](std::any& src)->Value {
return *std::any_cast<U&>(src);
},
[](std::any& src) {
++std::any_cast<U&>(src);
}
};
}
template<class U>
static vtable_t const* get_vtable() {
static const auto vtable = make_vtable<U>();
return &vtable;
}
public:
template<class U,
std::enable_if_t<!std::is_same<std::decay_t<U>, any_input_iterator>{}, bool> = true
>
any_input_iterator(U&& u):
vtable(get_vtable<std::decay_t<U>>()),
state(std::forward<U>(u))
{}
any_input_iterator& operator++() { vtable->inc(state); return *this; }
any_input_iterator operator++(int) { auto tmp = *this; ++*this; return tmp; }
reference operator*() { return vtable->get(state); }
friend bool operator==( any_input_iterator const& lhs, any_input_iterator const& rhs ) {
if (lhs.vtable != rhs.vtable) return false;
if (!lhs.vtable) return true;
return lhs.vtable->equal( lhs.state, rhs.state );
}
friend bool operator!=( any_input_iterator const& lhs, any_input_iterator const& rhs ) {
return !(lhs==rhs);
}
struct fake_ptr {
Value t;
Value* operator->()&&{ return std::addressof(t); }
};
fake_ptr operator->()const { return {**this}; }
};

可能有一些错别字,但这是基本的类型擦除。 Boost在这方面做得更好。

我只支持输入迭代器。 如果你想支持前向迭代器,你必须改变一些 typedef 并返回引用等。

any_input_iterator<int> begin();
any_input_iterator<int> end();

但是,这足以让某人迭代您的相关范围。

它会很慢,但它会起作用。

最新更新