自制迭代器的常量正确性
Const-correctness of self made iterators
总体目标
我管理对象集合(Real
的 Collection
作为简单示例)。然后我在我的集合上定义了迭代器。这意味着:iterator
、const_iterator
、reverse_iterator
和 const_reverse_iterator
。在这个例子中,我只会关注 iterator
和 const_iterator
,其他两个非常相似。
之后,我想在我的集合上定义一个过滤器,它根据特定条件保留或不保留元素。例如,仅保留 Real
个具有正值的实例。我也想仅在保留的元素上迭代我的集合。
我是如何实现这个集合的
对于这个例子,我在集合中的对象非常简单。目标只是拥有一个对象而不是原生类型:
struct Real
{
public:
double r;
};
然后我定义我的集合而不必知道里面的真实容器:
class Collection
{
public:
typedef std::vector<Real>::iterator iterator;
typedef std::vector<Real>::const_iterator const_iterator;
private:
std::vector<Real> data;
public:
Collection() : data() {}
Collection(unsigned long int n) : data(n) {}
Collection(unsigned long int n, const Real& x) : data(n,x) {}
Collection::iterator begin() { return this->data.begin(); }
Collection::iterator end() { return this->data.end(); }
Collection::const_iterator begin() const { return this->data.begin(); }
Collection::const_iterator end() const { return this->data.end(); }
};
在这个简单的例子中效果很好:
int main()
{
Collection c(5);
double k = 1.0;
for(Collection::iterator it = c.begin(); it != c.end(); ++it)
{
it->r = k;
k *= -2.0;
}
std::cout << "print c with Collection::iterator" << std::endl;
for(Collection::iterator it = c.begin(); it != c.end(); ++it)
std::cout << it->r << std::endl;
std::cout << "print c with Collection::const_iterator" << std::endl;
for(Collection::const_iterator it = c.begin(); it != c.end(); ++it)
std::cout << it->r << std::endl;
return 0;
}
并且此程序写入预期输出:
print with Collection::iterator
1
-2
4
-8
16
print with Collection::const_iterator
1
-2
4
-8
16
我是如何实现过滤器的
现在我想创建一个抽象过滤器,具有指向集合的引用或指针、迭代器以及通过过滤器接受值的抽象函数。对于第一步,我只写了没有迭代器的 class :
class CollectionFilter
{
private:
Collection& col;
public:
CollectionFilter(Collection& c) : col(c) {}
virtual ~CollectionFilter() {}
Collection& collection() { return this->col; }
iterator begin() { /* todo */ }
iterator end() { /* todo */ }
const_iterator begin() const { /* todo */ }
const_iterator end() const { /* todo */ }
virtual bool accept(const Real& x) const = 0;
};
然后,创建一个实现特定条件的新过滤器就很容易了:
class CollectionFilterPositive : public CollectionFilter
{
public:
CollectionFilterPositive(Collection& c) : CollectionFilter(c) {}
virtual ~CollectionFilterPositive() {}
virtual bool accept(const Real& x) const { return x.r >= 0.0; }
};
在过滤器中实现迭代器之前,我有一些评论/问题。
- 此过滤器适用于非常量
Collection&
,那么,真的需要 begin() const
和 end() const
函数吗?如果是,为什么?
- 我无法在
const Collection&
上应用过滤器,但这显然是我的目标所必需的。这样做的好方法是什么?我是否必须使用非常相似的代码将 class CollectionFilter
复制到 class CollectionFilterConst
?此外,这个解决方案对于必须从两个相似的 classes. 继承的用户来说是相当混乱的
然后,让我们来看看迭代器的实现。对于这个例子,我只写了 iterator
而没有写 const_iterator
。我将此添加到我的 class :
class CollectionFilter
{
public:
class iterator
{
private:
CollectionFilter* filter;
Collection::iterator iter;
public:
iterator(CollectionFilter* f, Collection::iterator i) : filter(f), iter(i) {}
iterator(const iterator& i) : filter(i.filter), iter(i.iter) {}
iterator& operator = (const iterator& i) { this->filter = i.filter; this->iter = i.iter; return *this; }
iterator& operator ++ ()
{
if(this->iter != this->filter->collection().end())
{
do
{
++this->iter;
} while(this->iter != this->filter->collection().end() && !this->filter->accept(*this->iter));
}
}
iterator operator ++ (int) { /* similar */ }
Real& operator * () { return *this->iter; }
Collection::iterator operator -> () { return this->iter; }
bool operator == (const iterator& i) const { return this->iter == i.iter; }
bool operator != (const iterator& i) const { return this->iter != i.iter; }
};
public:
iterator begin()
{
Collection::iterator it = this->col.begin();
if(!this->accept(*it)) ++it;
return CollectionFilter::iterator(this,it);
}
iterator end()
{
Collection::iterator it = this->col.end();
return CollectionFilter::iterator(this,it);
}
};
这在这个简单的例子中也很有效
int main()
{
Collection c(5);
double k = 1.0;
for(Collection::iterator it = c.begin(); it != c.end(); ++it)
{
it->r = k;
k *= -2.0;
}
std::cout << "print c with CollectionFilterPositive::iterator" << std::endl;
CollectionFilterPositive fc(c);
for(CollectionFilterPositive::iterator it = fc.begin(); it != fc.end(); ++it)
std::cout << it->r << std::endl;
return 0;
}
给出预期的输出:
print with CollectionFilterPositive::iterator
1
4
16
再次,一些问题:
- 我对这种方法完全错了吗?
- 我想我必须复制
CollectionFilter::iterator
的代码来实现 CollectionFilter::const_iterator
,只需稍作修改。有没有办法避免重复此代码(写 8 次,如果我计算重复的 class CollectionFilterConst
和反向迭代器)?
- 我对代码的常量正确性感到不满意。你看到一些问题了吗?
提前致谢!
- This filter works on a non-
const
Collection&
, then, are the begin() const
and end() const
function really required ? And if yes, why ?
- I can't apply the filter on a
const Collection&
, but it's clearly required for my goal. What could be a good way to do that ? Have I to duplicate the class CollectionFilter
to a class CollectionFilterConst
with a very similar code ? Moreover this solution is quite confusing for the user having to inherit from two similar classes.
这些问题非常相关。基本上,将过滤限制为非常量 Collection
有意义吗?这对我来说意义不大。我们根本不修改 CollectionFilter
对象,只修改底层的 Collection
对象(可能),并且 Filter
的功能与 Collection
是否无关是 const
。把它们放在一起,它需要一个模板:
template <typename C>
class Filter {
static_assert(std::is_same<
std::decay_t<C>,
Collection
>::value, "Can only filter a Collection.");
using collection_iterator = decltype(std::declval<C&>().begin());
C& collection_;
public:
Filter(C& collection) : collection_(collection) { }
struct iterator {
/* TODO, use collection_iterator */
};
iterator begin() const { /* TODO */ };
iterator end() const { /* TODO */ };
};
这样,Filter<Collection>::collection_iterator
就是Collection::iterator
,Filter<const Collection>::collection_iterator
就是Collection::const_iterator
。你不能做 Filter<std::vector<int>>
.
这种方法也能回答您的其他问题 - 这是一种 const
正确的、非重复的过滤任何集合的方法。
为了避免额外的输入,您还可以创建一个构建器函数:
template <typename <typename> class F, typename C>
F<C> makeFilter(C& collection) {
return F<C>(collection);
}
auto filter = makeFilter<CollectionFilterPositive>(some_collection);
filter
的迭代器的 const
-ness 将取决于 some_collection
.
的 const
-ness
我还会研究 Boost.IteratorFacade 以编写 Filter::iterator
,它会为您节省一些时间和一些麻烦。
我建议放弃 CollectionFilter
class,取而代之的是 Collection::filter_iterator_tmpl
模板 class,有两个实例化 Collection::filter_iterator
和 Collection::const_filter_iterator
.
Collection::filter_iterator_tmpl
可以这样实现:
class Collection {
template<typename Iterator, typename Predicate>
class filter_iterator_tmpl :
public std::iterator<std::input_iterator_tag, typename Iterator::value_type, typename Iterator::difference_type, typename Iterator::pointer, typename Iterator::reference> {
private:
Iterator underlying_iterator_;
Predicate predicate_;
public:
filter_iterator_tmpl& operator++() {
do {
++ underlying_iterator_;
} while(! predicate_(*underlying_iterator_));
return *this;
}
typename Iterator::reference operator*() const {
return *underlying_iterator_;
}
....
}
};
可以通过让 Predicate
成为具有 virtual bool PolymorphicPredicate::operator(Real) const
函数的多态函数体来添加多态性。
Collection
将定义实际的过滤器迭代器:
class Collection {
private:
template<typename Iterator, typename Predicate>
class filter_iterator_tmpl;
public:
template<typename Predicate>
using filter_iterator = filter_iterator_tmpl<Collection::iterator, Predicate>;
template<typename Predicate>
using const_filter_iterator = filter_iterator_tmpl<Collection::const_iterator, Predicate>;
template<typename Predicate>
filter_iterator<Predicate> begin_filter(const Predicate& pred);
template<typename Predicate>
const_filter_iterator<Predicate> begin_filter(const Predicate& pred) const;
}
Boost 以类似的方式实现通用 "Filter Iterator":http://www.boost.org/doc/libs/1_46_1/libs/iterator/doc/filter_iterator.html
作为独立的 class,而不是作为容器的一部分 class。
关于常量正确性:
C++ 中的容器(std::vector
、std::map
、std::string
等)拥有它们的内容对象:它们创建和删除它们,并且需要确保通过对容器的常量访问,您也只获得对内容对象的常量访问。需要实施它们以强制执行此操作,因为它们访问分配的存储所依据的底层指针没有这种所有权概念。这就是为什么他们需要有两个版本的迭代器(iterator
和 const_iterator
)。
迭代器本身不拥有该对象:通过对 iterator
的常量访问,您无法推进迭代器,但您仍然可以获得对该对象的非常量访问。
问题 1/2:
CollectionFilter
是有问题的,因为它不拥有它提供访问权限的对象,但对过滤器的 const/non-const 访问权限应该只提供对对象的 const/non-const 访问权限。
因为它持有对 Collection
的引用,并且它应该适用于对 Collection
的常量和非常量访问,所以需要两个版本 CollectionFilter
和 ConstCollectionFilter
用这种方法。
问题四:
一旦从一个 const 正确的容器对象拆分为两个 classes 用于 const 和非常量访问,就必然会有一些代码重复。
模板避免了必须手动实现两个版本。还有一些额外的复杂性,例如将 iterator
与 const_iterator
进行比较,以及从 iterator
构建 const_iterator
而不是相反...
问题 3/5:
见上文。
总体目标
我管理对象集合(Real
的 Collection
作为简单示例)。然后我在我的集合上定义了迭代器。这意味着:iterator
、const_iterator
、reverse_iterator
和 const_reverse_iterator
。在这个例子中,我只会关注 iterator
和 const_iterator
,其他两个非常相似。
之后,我想在我的集合上定义一个过滤器,它根据特定条件保留或不保留元素。例如,仅保留 Real
个具有正值的实例。我也想仅在保留的元素上迭代我的集合。
我是如何实现这个集合的
对于这个例子,我在集合中的对象非常简单。目标只是拥有一个对象而不是原生类型:
struct Real
{
public:
double r;
};
然后我定义我的集合而不必知道里面的真实容器:
class Collection
{
public:
typedef std::vector<Real>::iterator iterator;
typedef std::vector<Real>::const_iterator const_iterator;
private:
std::vector<Real> data;
public:
Collection() : data() {}
Collection(unsigned long int n) : data(n) {}
Collection(unsigned long int n, const Real& x) : data(n,x) {}
Collection::iterator begin() { return this->data.begin(); }
Collection::iterator end() { return this->data.end(); }
Collection::const_iterator begin() const { return this->data.begin(); }
Collection::const_iterator end() const { return this->data.end(); }
};
在这个简单的例子中效果很好:
int main()
{
Collection c(5);
double k = 1.0;
for(Collection::iterator it = c.begin(); it != c.end(); ++it)
{
it->r = k;
k *= -2.0;
}
std::cout << "print c with Collection::iterator" << std::endl;
for(Collection::iterator it = c.begin(); it != c.end(); ++it)
std::cout << it->r << std::endl;
std::cout << "print c with Collection::const_iterator" << std::endl;
for(Collection::const_iterator it = c.begin(); it != c.end(); ++it)
std::cout << it->r << std::endl;
return 0;
}
并且此程序写入预期输出:
print with Collection::iterator
1
-2
4
-8
16
print with Collection::const_iterator
1
-2
4
-8
16
我是如何实现过滤器的
现在我想创建一个抽象过滤器,具有指向集合的引用或指针、迭代器以及通过过滤器接受值的抽象函数。对于第一步,我只写了没有迭代器的 class :
class CollectionFilter
{
private:
Collection& col;
public:
CollectionFilter(Collection& c) : col(c) {}
virtual ~CollectionFilter() {}
Collection& collection() { return this->col; }
iterator begin() { /* todo */ }
iterator end() { /* todo */ }
const_iterator begin() const { /* todo */ }
const_iterator end() const { /* todo */ }
virtual bool accept(const Real& x) const = 0;
};
然后,创建一个实现特定条件的新过滤器就很容易了:
class CollectionFilterPositive : public CollectionFilter
{
public:
CollectionFilterPositive(Collection& c) : CollectionFilter(c) {}
virtual ~CollectionFilterPositive() {}
virtual bool accept(const Real& x) const { return x.r >= 0.0; }
};
在过滤器中实现迭代器之前,我有一些评论/问题。
- 此过滤器适用于非常量
Collection&
,那么,真的需要begin() const
和end() const
函数吗?如果是,为什么? - 我无法在
const Collection&
上应用过滤器,但这显然是我的目标所必需的。这样做的好方法是什么?我是否必须使用非常相似的代码将 classCollectionFilter
复制到 classCollectionFilterConst
?此外,这个解决方案对于必须从两个相似的 classes. 继承的用户来说是相当混乱的
然后,让我们来看看迭代器的实现。对于这个例子,我只写了 iterator
而没有写 const_iterator
。我将此添加到我的 class :
class CollectionFilter
{
public:
class iterator
{
private:
CollectionFilter* filter;
Collection::iterator iter;
public:
iterator(CollectionFilter* f, Collection::iterator i) : filter(f), iter(i) {}
iterator(const iterator& i) : filter(i.filter), iter(i.iter) {}
iterator& operator = (const iterator& i) { this->filter = i.filter; this->iter = i.iter; return *this; }
iterator& operator ++ ()
{
if(this->iter != this->filter->collection().end())
{
do
{
++this->iter;
} while(this->iter != this->filter->collection().end() && !this->filter->accept(*this->iter));
}
}
iterator operator ++ (int) { /* similar */ }
Real& operator * () { return *this->iter; }
Collection::iterator operator -> () { return this->iter; }
bool operator == (const iterator& i) const { return this->iter == i.iter; }
bool operator != (const iterator& i) const { return this->iter != i.iter; }
};
public:
iterator begin()
{
Collection::iterator it = this->col.begin();
if(!this->accept(*it)) ++it;
return CollectionFilter::iterator(this,it);
}
iterator end()
{
Collection::iterator it = this->col.end();
return CollectionFilter::iterator(this,it);
}
};
这在这个简单的例子中也很有效
int main()
{
Collection c(5);
double k = 1.0;
for(Collection::iterator it = c.begin(); it != c.end(); ++it)
{
it->r = k;
k *= -2.0;
}
std::cout << "print c with CollectionFilterPositive::iterator" << std::endl;
CollectionFilterPositive fc(c);
for(CollectionFilterPositive::iterator it = fc.begin(); it != fc.end(); ++it)
std::cout << it->r << std::endl;
return 0;
}
给出预期的输出:
print with CollectionFilterPositive::iterator
1
4
16
再次,一些问题:
- 我对这种方法完全错了吗?
- 我想我必须复制
CollectionFilter::iterator
的代码来实现CollectionFilter::const_iterator
,只需稍作修改。有没有办法避免重复此代码(写 8 次,如果我计算重复的 classCollectionFilterConst
和反向迭代器)? - 我对代码的常量正确性感到不满意。你看到一些问题了吗?
提前致谢!
- This filter works on a non-
const
Collection&
, then, are thebegin() const
andend() const
function really required ? And if yes, why ?- I can't apply the filter on a
const Collection&
, but it's clearly required for my goal. What could be a good way to do that ? Have I to duplicate theclass CollectionFilter
to aclass CollectionFilterConst
with a very similar code ? Moreover this solution is quite confusing for the user having to inherit from two similar classes.
这些问题非常相关。基本上,将过滤限制为非常量 Collection
有意义吗?这对我来说意义不大。我们根本不修改 CollectionFilter
对象,只修改底层的 Collection
对象(可能),并且 Filter
的功能与 Collection
是否无关是 const
。把它们放在一起,它需要一个模板:
template <typename C>
class Filter {
static_assert(std::is_same<
std::decay_t<C>,
Collection
>::value, "Can only filter a Collection.");
using collection_iterator = decltype(std::declval<C&>().begin());
C& collection_;
public:
Filter(C& collection) : collection_(collection) { }
struct iterator {
/* TODO, use collection_iterator */
};
iterator begin() const { /* TODO */ };
iterator end() const { /* TODO */ };
};
这样,Filter<Collection>::collection_iterator
就是Collection::iterator
,Filter<const Collection>::collection_iterator
就是Collection::const_iterator
。你不能做 Filter<std::vector<int>>
.
这种方法也能回答您的其他问题 - 这是一种 const
正确的、非重复的过滤任何集合的方法。
为了避免额外的输入,您还可以创建一个构建器函数:
template <typename <typename> class F, typename C>
F<C> makeFilter(C& collection) {
return F<C>(collection);
}
auto filter = makeFilter<CollectionFilterPositive>(some_collection);
filter
的迭代器的 const
-ness 将取决于 some_collection
.
const
-ness
我还会研究 Boost.IteratorFacade 以编写 Filter::iterator
,它会为您节省一些时间和一些麻烦。
我建议放弃 CollectionFilter
class,取而代之的是 Collection::filter_iterator_tmpl
模板 class,有两个实例化 Collection::filter_iterator
和 Collection::const_filter_iterator
.
Collection::filter_iterator_tmpl
可以这样实现:
class Collection {
template<typename Iterator, typename Predicate>
class filter_iterator_tmpl :
public std::iterator<std::input_iterator_tag, typename Iterator::value_type, typename Iterator::difference_type, typename Iterator::pointer, typename Iterator::reference> {
private:
Iterator underlying_iterator_;
Predicate predicate_;
public:
filter_iterator_tmpl& operator++() {
do {
++ underlying_iterator_;
} while(! predicate_(*underlying_iterator_));
return *this;
}
typename Iterator::reference operator*() const {
return *underlying_iterator_;
}
....
}
};
可以通过让 Predicate
成为具有 virtual bool PolymorphicPredicate::operator(Real) const
函数的多态函数体来添加多态性。
Collection
将定义实际的过滤器迭代器:
class Collection {
private:
template<typename Iterator, typename Predicate>
class filter_iterator_tmpl;
public:
template<typename Predicate>
using filter_iterator = filter_iterator_tmpl<Collection::iterator, Predicate>;
template<typename Predicate>
using const_filter_iterator = filter_iterator_tmpl<Collection::const_iterator, Predicate>;
template<typename Predicate>
filter_iterator<Predicate> begin_filter(const Predicate& pred);
template<typename Predicate>
const_filter_iterator<Predicate> begin_filter(const Predicate& pred) const;
}
Boost 以类似的方式实现通用 "Filter Iterator":http://www.boost.org/doc/libs/1_46_1/libs/iterator/doc/filter_iterator.html 作为独立的 class,而不是作为容器的一部分 class。
关于常量正确性:
C++ 中的容器(std::vector
、std::map
、std::string
等)拥有它们的内容对象:它们创建和删除它们,并且需要确保通过对容器的常量访问,您也只获得对内容对象的常量访问。需要实施它们以强制执行此操作,因为它们访问分配的存储所依据的底层指针没有这种所有权概念。这就是为什么他们需要有两个版本的迭代器(iterator
和 const_iterator
)。
迭代器本身不拥有该对象:通过对 iterator
的常量访问,您无法推进迭代器,但您仍然可以获得对该对象的非常量访问。
问题 1/2:
CollectionFilter
是有问题的,因为它不拥有它提供访问权限的对象,但对过滤器的 const/non-const 访问权限应该只提供对对象的 const/non-const 访问权限。
因为它持有对 Collection
的引用,并且它应该适用于对 Collection
的常量和非常量访问,所以需要两个版本 CollectionFilter
和 ConstCollectionFilter
用这种方法。
问题四:
一旦从一个 const 正确的容器对象拆分为两个 classes 用于 const 和非常量访问,就必然会有一些代码重复。
模板避免了必须手动实现两个版本。还有一些额外的复杂性,例如将 iterator
与 const_iterator
进行比较,以及从 iterator
构建 const_iterator
而不是相反...
问题 3/5: 见上文。