如何在c++中迭代集合

How do I iterate over collections generically in C++?

本文关键字:迭代 集合 c++      更新时间:2023-10-16

简单地说,如果我有一个集合和一个向量,我如何创建一个通用的方法来处理这两个参数呢?

我要做的就是遍历这两种类型的集合。听起来应该是微不足道的,但我遗漏了一些东西。

void printMeSomeStrings(somebaseclass<string> strings) {
  for (auto& str : strings) {
    cout << str << endl;
  }
}
在c#中,我会传递IEnumerable或类似的东西。然后我可以遍历这个集合。

任何解释答案的一般性阅读都将受到赞赏。

您可以使用模板。例如:

#include <iostream>
template<typename C>
void foo(C const& c)
{
    std::cout << "{ ";
    for (auto const& x : c)
    {
        std::cout << x << " ";
    }
    std::cout << "}";
}

你可以这样使用它:

#include <set>
#include <vector>
int main()
{
    std::vector<int> v = {1, 2, 3};
    foo(v);
    std::cout << std::endl;
    std::set<std::string> s = {"Hello,", "Generic", "World!"};
    foo(s);
}

生活例子。

这就是设计迭代器的目的。

template <class It>
void print_some_strings(It first, It last) {
    while (first != last)
        std::cout << *first++ << 'n';
}

第一个选项是将执行迭代的代码放在模板中。这需要将实现公开给所有使用它的人,这有缺点。

基本上,将类型C作为template参数,然后根据该类型C编写代码。

template<typename C>
void printMeSomeStrings(C&& strings) {
  for (auto const& str : strings) {
    cout << str << endl;
  }
}

如果你想在接口和实现之间有一个强大的屏障,c++ 11的方法是在for -iterable容器上进行类型擦除,然后公开for -iterable容器,就像std::function的工作方式一样。

这个比较棘手。我个人认为编写一个for_each函数比编写一个完整的迭代适配器更容易。如果你想要完整的容器迭代类型的擦除对象,从boost开始,或者在下面问我,我可能会做。

for_each适配器很简单,但是

#include <functional>
#include <utility>
#include <iterator>
#include <memory>
template<typename T>
struct for_each_helper_interface {
  virtual ~for_each_helper_interface() {}
  virtual void for_each( std::function< void(T) > const& ) = 0;
};
template<typename C, typename T>
struct for_each_helper:for_each_helper_interface<T> {
  C& c;
  for_each_helper( C& in ):c(in) {}
  virtual void for_each( std::function< void(T) > const& f ) override final {
    for( auto&& x:c ) {
      f(x);
    }
  }
};
template<typename T>
struct for_each_adaptor {
  std::unique_ptr<for_each_helper_interface<T>> pImpl;
  void for_each( std::function< void(T) > const& f ) {
    if (pImpl) {
      pImpl->for_each(f);
    }
  }
  template<typename C>
  for_each_adaptor( C&& c ): pImpl( new for_each_helper<C, T>( std::forward<C>(c) ) ) {}
};

将类型擦除T的容器(或可转换为T的类型!)并公开for_each方法,该方法允许您迭代容器的内容。像这样使用:

#include <set>
#include <iostream>
#include <vector>
void print_stufF( for_each_adaptor<std::string const&> c ) {
  c.for_each([&](std::string const&s){
    std::cout << s << "n";
  });
}
int main() {
   std::set<std::string> s;
   s.insert("hello");
   s.insert("world");
   print_stuff(s);
   std::vector<std::string> v;
   v.push_back("hola");
   v.push_back("bola");
   print_stuff(v);
 }

这里的情况是,对于用于构造适配器的每种类型,我们为每种类型构建一个自定义实现。然后存储一个指向该自定义类的抽象基类的指针,并为每次对它的调用进行重定向。

这意味着任何专门化std::begin或定义其自己的开始的东西都不需要关联:我们在使用点创建临时关系。

实例:http://ideone.com/xOqBkI

在c#中,我会传递IEnumerable或类似的东西。

c++使用更python化的duck类型方法来定义接口(在c++中通常称为概念),而不是使用继承。要在c++中进行duck输入,可以使用如下的模板函数:

template<typename C>
void printMeSomeStrings(const C& strings) 
{
    for (const auto& str : strings) 
    {
        cout << str << endl;
    }
}

在python中,duck类型是在运行时完成的,但在c++中,它是在编译时完成的,所以duck类型没有运行时成本,并且在编译时也检查了所有内容。

这里是关于c++的更多信息,以帮助查找信息。首先,等价于IEnumerator<T>的是c++中的迭代器。这里有一页介绍了不同的迭代器类别,以及需要为迭代器实现什么。由于遗留原因,迭代器是按照C中的指针建模的,这使得您可以将C数组与标准c++算法一起使用。

但是,与IEnumerator<T>不同,迭代器必须成对出现。开始和结束的迭代器(在最后一个元素的后面)。因此,c++中IEnumerable<T>的等价物称为range。在c++ 11中,范围由两个自由函数begin(T)end(T)定义(也可以作为成员函数.begin().end()实现)。

通过将概念(又名接口)定义为两个自由函数,而不是使用继承,可以非侵入性地实现范围。例如,如果你有一些使用C风格链表的旧api。

它们现在可以被改编为c++ 11范围,并在c++ for循环中使用。