【问题标题】:How to write a function that takes an iterator or collection in a generic way?如何编写一个以通用方式获取迭代器或集合的函数?
【发布时间】:2009-09-19 08:11:53
【问题描述】:

在过去 8 年左右的时间里,我几乎完全是一名 Java 程序员,最近我又开始使用 C++。这是我遇到的关于 C++ STL 和 Java 中的迭代器的问题。

在 Java 中,您可以编写一个接受迭代器的方法,如下所示:

void someMethod(Iterator<String> data) {
    // ...
}

您传入一个Iterator,该方法不需要知道该迭代器的底层集合是什么,这很好。

在 C++ 中,没有通用的迭代器基类(据我所知)。我必须写一个这样的函数:

void some_function(std::vector<std::string>::const_iterator data) {
    // ...
}

换句话说,some_function 知道迭代器是 vector 上的迭代器。这不好,因为无论迭代器的底层集合是什么,我都希望函数能够工作。

如何在 C++ 中做到这一点?如果真的不可能,那么在 C++ 中创建一个将集合作为参数的函数的最佳方法是什么,但不需要知道集合的确切类型是什么?

附录

感谢您的回答。除了答案之外,我在The C++ Standard Library: A Tutorial and Reference (作者 Nicolai M. Josuttis)一书的第 7.5 段(迭代器特征)中找到了一些很好的信息。第 7.5.1 段解释了如何为不同的迭代器类别编写专门的函数版本。

【问题讨论】:

    标签: java c++ collections


    【解决方案1】:

    您可能想要考虑一个函数模板。看看std&lt;algorithm&gt; 的一些函数模板是如何工作的,例如std::for_each

    例如

    template< class Iterator >
    void some_function( Iterator first, Iterator last )
    {
        // ...
    }
    

    然后您可以调用从该模板生成的具有多种可迭代范围的函数。

    例如

    std::vector< double > my_doubles;
    // ... populate doubles
    some_function( my_doubles.begin(), my_doubles.end() );
    
    
    std::set< Custom > my_custom_class_set;
    // ... populate ...
    some_function( my_custom_class_set.begin(), my_custom_class_set.end() );
    
    int raw_array[50];
    // ... populate ...
    some_function( raw_array, raw_array + 50 );
    

    【讨论】:

    • 关键是,在 C++ 中,模板是在编译时实例化的,因此您不需要为迭代器使用公共基类,只要它们都具有相同的“形状”。
    • 看看像 for_each() 这样的标准算法是如何做到这一点的,这是个好主意,谢谢。
    【解决方案2】:

    最好通过命名约定来指示迭代器的种类,以及迭代器需要拥有的属性种类。以下是迭代器的一些常见命名约定:

    模板 void foo_iterator(迭代器开始,迭代器结束) { typedef typename std::iterator_traits::value_type T; …… } 模板 void foo_random_iterator(RandomIterator 开始,RandomIterator 结束) { typedef typename std::iterator_traits::value_type T; …… } 模板 void foo_forward_iterator(ForwardIterator 开始,ForwardIterator 结束) { typedef typename std::iterator_traits::value_type T; …… } 模板 void foo_forward_iterator(ReverseIterator 开始,ReverseIterator 结束) { typedef typename std::iterator_traits::value_type T; …… } 模板 void foo_input_iterator(InputIterator 开始,InputIterator 结束) { typedef typename std::iterator_traits::value_type T; …… } 模板 void foo_output_iterator(OutputIterator out) { // 我们没有类型 T,因为我们不能“总是” // 知道类型,因为这种类型的迭代器是一个接收器。 …… }

    以下是序列类型容器的通用定义,包括vector和deque。

    模板 类序列> 内联 void foo_sequence(Sequence& 序列) { …… }

    【讨论】:

      【解决方案3】:

      这是 C++ 和 Java 之间最大差异之一的示例。 Java 唯一的抽象工具是运行时多态性(接口和抽象类)。在 C++ 中,您不限于此。您可以为类型创建别名并让类具有其他关联/嵌套类型。在许多情况下,它可以让您摆脱运行时多态性。编译时类型的泛型具有非常快的优点(没有虚函数调用,内联可能性)。此外,当您没有垃圾收集器时,它可以简化生命周期管理。您可以简单地在堆栈上创建对象。

      这是一个(未经测试的)示例:

      template<typename Iter>
      typename std::iterator_traits<Iter>::value_type
      sum(Iter begin, Iter end) {
         typedef typename std::iterator_traits<Iter>::value_type vt;
         vt accum = vt();
         while (begin!=end) {
            accum += *begin;
            ++begin;
         }
         return accum;
      }
      

      在这里,“Iter”只是一个名称。它实际上并没有对类型施加任何约束。如果您想使用不是迭代器的类型(至少在结构意义上)实例化此模板,您将收到编译时错误(编译时鸭子类型)。因此,您的部分工作是记录您期望的类型。这通常通过选择模板参数(即 ForwardIterator)和 cmets 的一些描述性名称来完成。

      我还应该提到,如果您将此函数模板与不同种类的迭代器一起使用,则多个“求和”函数将被“实例化”。如果您不想要这种代码重复和/或真的需要运行时多态性,您可以应用一种称为“类型擦除”的技术。但是,迭代器的类型擦除不是标准库的一部分。此外,我从未觉得有必要将这种技术应用于迭代器。但是你会发现在 boost::any 和 boost::function 等其他库中使用类型擦除。

      您可以使用其他一些模板技巧来区分不同的迭代器类别(请参阅“标签调度”)或约束您的函数模板(请参阅“SFINAE”)。如果您对类型擦除感兴趣,请尝试使用谷歌搜索 c++、类型擦除、迭代器。您基本上创建了一个管理多态对象的句柄类(通过指针)。这个多态对象包装了一些您想要“擦除”(隐藏)其类型的其他对象。

      【讨论】:

      • "编译时鸭子类型" - 是的,您似乎无法指出 value_type 必须是特定类型,或者指定类型的其他约束。这是 C++ 的“概念”应该添加到未来版本的 C++ 中吗?
      • 是的。概念是一种类型的类型系统。不幸的是,他们没有进入 C++0x。但是你可以用原生概念支持做的一些事情可以用“模板技巧”来模拟——包括将你的函数模板限制为某种迭代器类型。
      【解决方案4】:

      您可以使用header file 并指定迭代器必须支持的最低要求。

      所以在上面的例子中,你可能想这样重写函数:

      template<typename T>
      void some_function(std::forward_iterator<T> data) {
         ...
      }
      

      对于需要能够仅通过集合向前移动迭代器 (++) 的东西。

      【讨论】:

      • std 命名空间中没有 forward_iterator。有一个forward_iterator_tag,但这不是一个迭代器,它是一条信息,您可以从使用特征类的迭代器中获得。
      • 这或多或少是一对一翻译成C++的Java示例;像这样的东西是我首先尝试的,它不起作用。
      • 我猜你是想说: template void some_function(std::iterator<:forward_iterator_tag> it) { } 这是一个不正确的方法,因为有不保证传入的迭代器是从 std::iterator 派生的。 atm 定义迭代器的最佳方式是按属性命名它们并希望用户理解。例如:InputIterator、ForwardIterator、RandomIterator 等......希望在未来引入概念时,事情会变得更加可口。
      • 您不必依赖命名约定,您可以将函数模板调用分派给(例如)具有从iterator_traits 构造的额外模板参数的类模板成员。然后,您可以根据特征参数进行部分专业化,以提供优化的算法,例如随机访问迭代器。这依赖于有一个有效的iterator_traits 特化来选择最佳算法,但这意味着您的模板将适用于其他迭代器和指针。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-04-30
      • 2021-12-14
      • 2013-10-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多