使用类型抹除和反向迭代器(reverse_iterator)。

4

我有一个类,其中包含和管理一系列对象。为了避免泄露这些对象的存储方式,同时允许迭代它们,我决定使用类型擦除,使用boost::any_iterator

 using my_erased_type_iterator = boost::range_detail::any_iterator<
    MyClass,
    boost::bidirectional_traversal_tag,
    MyClass&, 
    std::ptrdiff_t>;

我在MyClass中定义了一个函数Begin()End(),它们只是将容器的begin()end()函数作为my_erased_type_iterator返回。它完全按照我的要求工作,除了MyClass外的任何人都不知道我正在使用向量来存储对象,也没有访问容器的权限,除了我在Myclass接口中公开的函数。

现在,由于很多原因,我需要通过反向迭代器进行迭代。我还需要知道反向迭代器之后的下一个元素(类似于在正常迭代器上调用std::next(),这对于反向迭代器来说已经不那么简单了),我可能还需要在该反向迭代器上调用erase()等函数。

所以我的问题是:是否有一种优雅的方式可以将类型抹除与反向迭代器(以及前向和反向的const版本)一起使用?我应该使用前向类型抹除迭代器并倒序迭代吗?我想到我可能是错误地解决了这个问题,所以我愿意接受任何建议或澄清我的问题(如果需要的话)。


你应该明白,你的选择会带来性能成本,并且迭代器失效规则(对于每个容器都是独特的)会泄露你正在使用的容器。 (当你的迭代器失效时,这是你接口的一部分) - Yakk - Adam Nevraumont
啊,我现在看到了与 erase() 相关的请求 - 由于所有的 "类型擦除",我错过了它。你不能使用 "烘焙" 的迭代器来使用 erase,因为没有(不麻烦的)方法可以回到底层迭代器。/cc @Yakk - sehe
@sehe 唉,他们没有包含 std::function::target<T>() 的等效物?懒鬼们。 - Yakk - Adam Nevraumont
2个回答

1
请注意,any_iterator 是一个实现细节。
首先回答您的直接问题,然后展示使用 Boost Range 的公共 API 中预期的 any_range<> 方法。
1. make_reverse_iterator 您可以简单地使用标准 c++14 或 boost 中的 make_reverse_iterator 工具。
- 标准 c++14 http://en.cppreference.com/w/cpp/iterator/make_reverse_iterator - boost http://www.boost.org/doc/libs/1_60_0/libs/iterator/doc/reverse_iterator.html 在 Coliru 上实时运行
#include <boost/range.hpp>
#include <boost/range/any_range.hpp>

struct MyClass {
    int i;
};

using my_erased_type_iterator = boost::range_detail::any_iterator<
    MyClass,
    boost::bidirectional_traversal_tag,
    MyClass&, 
    std::ptrdiff_t>;

#include <iostream>
#include <vector>

int main() {
    using namespace boost;
    std::vector<MyClass> const v { {1}, {2}, {3}, {4} };

    for (auto& mc : make_iterator_range(
                make_reverse_iterator(v.end()),
                make_reverse_iterator(v.begin())))
    {
        std::cout << mc.i << " ";
    }
}

打印

4 3 2 1 

2. reversed范围适配器:

或者,您可以完全使用范围样式并使用any_range<>

在Coliru上实时演示

int main() {
    std::vector<MyClass> const v { {1}, {2}, {3}, {4} };

    boost::any_range_type_generator<decltype(v)>::type x = reverse(v);

    for (my_erased_type_const_iterator f = boost::begin(x), l = boost::end(x); f!=l; ++f) {
        std::cout << f->i << " ";
    }

}

@Yakk 你的意思是什么?在我的样例中,它们是const迭代器,并且根据使用情况是双向/随机。能否澄清一下? - sehe
OP想要使用反向迭代器来擦除元素,以确定擦除的位置(具体细节不太清楚)。 - Yakk - Adam Nevraumont
我不明白你的句子意思,不好意思。 - sehe
假设我有一个反向迭代器bob。假设我使用bob作为要删除的位置进行擦除。它是否会删除std::prev(bob.base())?erase函数返回什么?这些只是猜测:OP说她想使用反向迭代器进行删除。确切含义不太清楚。 - Yakk - Adam Nevraumont
问题在于 bob.base() 被擦除了,所以它甚至无法编译。@Yakk (http://coliru.stacked-crooked.com/a/960dfa749ad53932)。我认为这个问题更多地涉及到被擦除的迭代器,而不仅仅是反向迭代器。反向部分相当平凡(并且 OP 可以解决他需要的语义)。 - sehe
显示剩余2条评论

1

只需反转类型擦除迭代器即可。

这暴露了.base(),这意味着擦除几乎和擦除类型擦除的前向迭代器一样容易。

顺便说一句,根据我的经验,您的设计具有性能成本而收益微不足道。底层容器的迭代器无效规则仍然适用,因此您的类的用户必须知道底层容器是什么!(或者,他们已经知道得太多了)。更换容器将无法提供足够相似的行为,因此尽管您花费相当昂贵的尝试隐藏它,但您的容器仍然被锁定。


由于我仍处于程序开发的早期阶段,我想避免将类“绑定”在一起。类型擦除似乎是实现这一点的好方法。当我拥有度量和分析器向我展示性能成本对设计优势来说太大时,它可能会被抛弃,但目前,我希望将良好的设计优先于性能。 - user1784377
1
@user1784377 当然,除了非关联容器之外,很少有理由使用其他东西。这种擦除可能会让您稍后在同一向量中存储其他内容以及要公开的内容。但是您应该记住的规则之一是“您可能不需要它”:尽早编写大量(可能代价高昂)的抽象可能会在一年内解决某些问题,但更有可能您不需要该抽象。项目将死亡,可能被过度抽象所杀,或者不需要该抽象。 - Yakk - Adam Nevraumont
我知道“你可能不需要它”的概念,因此我倾向于避免编写实际不需要的代码,但我认为这种理念也适用于设计和抽象。虽然我希望这个项目能在一年后仍然存在,而且我不想之后再添加那些抽象层,但我认为你说得对,现在消除额外的抽象可能会简化并加速整个开发过程。 - user1784377

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接