简体   繁体   中英

How to write a function that takes an iterator or collection in a generic way?

I've been a Java programmer almost exclusively for the past 8 years or so, and recently I've been playing with C++ again. Here's an issue that I've come up against with regards to iterators in C++ STL and Java.

In Java, you can write a method that takes an iterator like this:

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

You pass in an Iterator and the method does not need to know what the underlying collection of that iterator is, which is good.

In C++, there is no common base class for iterators (as far as I know). I'd have to write a function like this:

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

In other words, some_function knows that the iterator is an iterator over a vector . That's not good, because I want the function to work regardless of what the underlying collection of the iterator is.

How can I do this in C++? If it isn't really possible, then what is the best way to create a function in C++ that takes a collection as a parameter, but that doesn't need to know what the exact kind of collection is?

Addendum

Thanks for the answers. In addition to the answers I found some good information on this in paragraph 7.5 (Iterator Traits) of the book The C++ Standard Library: A Tutorial and Reference (by Nicolai M. Josuttis). Paragraph 7.5.1 explains how to write specialized versions of functions for different iterator categories.

You probably want to consider a function template. Look at how some of std <algorithm> function templates work such as std::for_each .

eg

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

You can then call a function generated from this template with many kinds of iterable ranges.

eg

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 );

Its best to indicate through a naming convention the kind of iterator and subsequently the kind of properties the iterator is required to posses. Below are some common naming conventions for iterators:

template<typename Iterator>
void foo_iterator(Iterator begin, Iterator end)
{
   typedef typename std::iterator_traits<Iterator>::value_type T;
   ....
}

template<typename RandomIterator>
void foo_random_iterator(RandomIterator begin, RandomIterator end)
{
   typedef typename std::iterator_traits<RandomIterator>::value_type T;
   ....
}

template<typename ForwardIterator>
void foo_forward_iterator(ForwardIterator begin, ForwardIterator end)
{
   typedef typename std::iterator_traits<ForwardIterator>::value_type T;
   ....
}

template<typename ReverseIterator>
void foo_forward_iterator(ReverseIterator begin, ReverseIterator end)
{
   typedef typename std::iterator_traits<ReverseIterator>::value_type T;
   ....
}

template<typename InputIterator>
void foo_input_iterator(InputIterator begin, InputIterator end)
{
   typedef typename std::iterator_traits<InputIterator>::value_type T;
   ....
}

template<typename OutputIterator>
void foo_output_iterator(OutputIterator out)
{
   // We don't have a type T, as we can't "always"
   // know the type, as this type of iterator is a sink.
   ....
}

Below is a generic definition for sequence type containers, which include vector and deque.

template <typename T,
          class Allocator,
          template <class,class> class Sequence>
inline void foo_sequence(Sequence<T,Allocator>& sequence)
{
   ....
}

This is an example of one of the big differences between C++ and Java. The only abstraction tool Java has is runtime polymorphism (interfaces and abstract classes). In C++ you're not limited to that. You can create aliases for types and let classes have other associated/nested types. It lets you get away without runtime polymorhism in many cases. The compile-time kind of genericity has the advantage of being quite fast (no virtual function calls, inlining possibilities). Also, it eases life-time management when you don't have a garbage collector. You can simply create the objects on the stack.

Here's an (untested) example:

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

Here, "Iter" ist just a name. It doesn't actually impose any constraints on the type. In case you want to instantiate this template with a type that is not an iterator (at least in a structural sense) you'll get a compile-time error (compile-time duck typing). So, part of your job is documenting what kind of type you are expecting. This is usually done by picking some descriptive names of template parameters (ie ForwardIterator) and comments.

I should also mention that multiple "sum" functions are going to be "instantiated" if you use this function template with varying kinds of iterators. If you don't want this kind of code duplication and/or really need runtime polymorphism you can apply a technique called "type erasure". Type erasure for iterators is not part of the standard library, though. Also, I never felt the need to apply this technique for iterators. But you'll find the use of type erasure in other libraries like boost::any and boost::function.

There are a couple of other template tricks you can use to differentiate between different iterator categories (see "tag dispatching") or constrain your function template (see "SFINAE"). If you're interested in type erasure try googling for c++, type erasure, iterator. You basically create a handle class that manages a polymorphic object (through a pointer). This polymorphic object wraps some other object whose type you want to "erase" (hide).

You can use the header file and specify the minimum requirements that the iterator must support.

So in the above example, you might want to rewrite the function as such:

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

for something that requires to be able to only move the iterator forward (++) through a collection.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM