Как перебирать коллекции в общем виде в C++?
проще говоря, если у меня есть набор и вектор, Как создать общий метод, который может обрабатывать оба параметра.
все, что я хочу сделать, это перебрать типы коллекций. Похоже, это должно быть тривиально, но я что-то упускаю.
void printMeSomeStrings(somebaseclass<string> strings) {
for (auto& str : strings) {
cout << str << endl;
}
}
В C# я бы прошел IEnumerable или что-то в этом роде. Тогда я мог бы повторить всю коллекцию.
общие читая объяснения ответ будут оценены.
4 ответов
первый вариант-поместить код, выполняющий итерацию в шаблон. Это требует разоблачения реализации для всех, кто ее использует, что имеет недостатки.
в основном, возьмите тип 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
функция проще, чем писать полномасштабный адаптер итерации. Если вам нужен полномасштабный объект итерации типа контейнера erasure, начните с 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
вы можете использовать шаблоны. Например:
#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) {
std::cout << *first++ << std::endl;
}
в C# я бы прошел IEnumerable или что-то в этом роде.
C++ использует более питонический подход duck typing для определения интерфейсов (обычно называемый концептом на C++), а не с помощью наследования. Чтобы сделать утку ввода в C++, вы используете функцию шаблона, как это:
template<typename C>
void printMeSomeStrings(const C& strings)
{
for (const auto& str : strings)
{
cout << str << endl;
}
}
в python ввод утки выполняется во время выполнения, но в C++ это делается во время компиляции, поэтому нет затрат времени выполнения для ввода утки, и все проверяется на время компиляции также.
вот дополнительная информация о C++, чтобы помочь в поиске информации. Во-первых, эквивалент IEnumerator<T>
является итератором в C++. здесь - это страница о различных категориях итераторов и о том, что необходимо реализовать для итераторов. По устаревшим причинам итераторы моделируются после указателей в C, что позволяет использовать массивы C со стандартными алгоритмами c++.
однако, в отличие от IEnumerator<T>
итераторы должны быть парными. - итератор до начала и конца (который является одним из последних элементов). Итак, эквивалент IEnumerable<T>
В C++ называется диапазон. В C++11, диапазон определяется двумя свободными функциями, begin(T)
и end(T)
(Он также может быть реализован как функция-член .begin()
и .end()
).
определив концепцию (интерфейс aka) как две свободные функции, в отличие от использования наследования, диапазоны могут быть реализованы неинтрузивно. Например, если у вас есть работа с некоторым устаревшим api, который использует C стиль связанных списков. Теперь их можно адаптировать как диапазон c++11 и использовать внутри цикла C++ for.