C ++ 11 — Как мне перебирать коллекции в целом в C ++?

Проще говоря, если у меня есть набор и вектор, как мне создать универсальный метод, который может обрабатывать оба в качестве параметров.

Все, что я хочу сделать, это перебрать любой из типов коллекций. Похоже, это должно быть тривиально, но я что-то упустил.

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

В C # я бы передавал IEnumerable или что-то в этом роде. Тогда я мог бы перебрать коллекцию.

Любое общее чтение, объясняющее ответ, будет оценено.

6

Решение

Первый вариант — поместить код, выполняющий итерации, в шаблон. Это требует раскрытия реализации всем, кто ее использует, что имеет недостатки.

В основном, взять тип C как template параметр, а затем написать свой код в терминах этого типа C,

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

Если вы хотите иметь сильный барьер между интерфейсом и реализацией, подход C ++ 11 будет заключаться в удалении типа на forконтейнер, а затем выставить forконтейнер, вроде как 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

2

Другие решения

Вы можете использовать шаблоны. Например:

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

Живой пример.

4

Это именно так для чего были созданы итераторы.

template <class It>
void print_some_strings(It first, It last) {
std::cout << *first++ << std::endl;
}
3

В C # я бы передавал IEnumerable или что-то в этом роде.

C ++ использует более питонический подход к утиной типизации для определения интерфейсов (обычно называемый концепцией в C ++) вместо использования наследования. Чтобы печатать на уток в C ++, вы используете функцию шаблона следующим образом:

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>итераторы должны приходить парами. Итератор до начала и до конца (то есть после последнего элемента). Итак, эквивалент IEnumerable<T> в C ++ называется диапазоном. В C ++ 11 диапазон определяется двумя свободными функциями, begin(T) а также end(T)(это также может быть реализовано как функция-член .begin() а также .end()).

Определяя концепцию (иначе интерфейс) как две свободные функции, в отличие от использования наследования, диапазоны могут быть реализованы не навязчиво. Так, например, если у вас есть работа с некоторыми устаревшими API, которые используют связанные списки в стиле C. Теперь они могут быть адаптированы как диапазон C ++ 11 и использоваться внутри цикла C ++ for.

0
По вопросам рекламы [email protected]