8

简而言之,如果我有一个集合和向量,我如何创建一个可以将两者都作为参数处理的通用方法。

我想做的就是遍历任何一种类型的集合。听起来应该是微不足道的,但我错过了一些东西。

void printMeSomeStrings(somebaseclass<string> strings) {
  for (auto& str : strings) {
    cout << str << endl;
  }
}

在 C# 中,我会传递 IEnumerable 或类似的东西。然后我可以遍历集合。

任何解释答案的一般性阅读将不胜感激。

4

4 回答 4

5

你可以使用模板。例如:

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

活生生的例子

于 2013-05-14T15:29:36.500 回答
3

正是迭代器的设计目的。

template <class It>
void print_some_strings(It first, It last) {
    while (first != last)
        std::cout << *first++ << '\n';
}
于 2013-05-14T21:36:37.747 回答
2

第一种选择是将执行迭代的代码放在模板中。这需要将实现暴露给使用它的每个人,这是有缺点的。

基本上,将类型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

于 2013-05-14T16:02:46.130 回答
0

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

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

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

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

这是有关 C++ 的更多信息,以帮助查找信息。首先,等效IEnumerator<T>于 C++ 中的迭代器。是一个关于不同迭代器类别的页面,以及需要为迭代器实现的内容。出于遗留原因,迭代器是根据 C 中的指针建模的,这使您可以将 C 数组与标准 C++ 算法一起使用。

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

通过将概念(又名接口)定义为两个自由函数,而不是使用继承,范围可以以非侵入方式实现。因此,例如,如果您使用了一些使用 C 样式链表的遗留 api。它们现在可以改编为 C++11 范围并在 C++ for 循环中使用。

于 2013-05-15T13:07:56.053 回答