Существует ли стандартный C++ эквивалент IEnumerable‹T› в C#?

Или безопасно использовать вектор, если перечислитель T просто перечисляет все элементы?


person derekhh    schedule 06.01.2012    source источник
comment
Существует эквивалент, который вы могли бы использовать в С++, не могли бы вы увидеть пример кода   -  person MethodMan    schedule 07.01.2012
comment
@DJKRAZE: Спасибо! Я просто пытаюсь увидеть, есть ли более подходящие подходы, кроме использования вектора, которые также могут позволить наши собственные реализации GetEnumerator() в C++.   -  person derekhh    schedule 07.01.2012


Ответы (4)


В C++ он не нужен, и вот почему:

C# поддерживает только динамический полиморфизм. Таким образом, для создания повторно используемого алгоритма вам нужен интерфейс, который будут реализовывать все итераторы. Это IEnumerator<T>, а IEnumerable<T> — это фабрика для возврата итератора.

С другой стороны, шаблоны C++ поддерживают утиную печать. Это означает, что вам не нужно ограничивать параметр универсального типа интерфейсом для доступа к членам — компилятор будет искать члены по имени для каждого отдельного экземпляра шаблона.

Контейнеры и итераторы C++ имеют неявные интерфейсы, эквивалентные .NET IEnumerable<T>, IEnumerator<T>, ICollection<T>, IList<T>, а именно:

Для контейнеров:

  • iterator и const_iterator определения типов
  • begin() функция-член - заполняет потребность в IEnumerable<T>::GetEnumerator()
  • end() функция-член -- вместо IEnumerator<T>::MoveNext() возвращаемого значения

Для прямых итераторов:

  • value_type определение типа
  • operator++ -- вместо IEnumerator<T>::MoveNext()
  • operator* и operator-> -- вместо IEnumerator<T>::Current
  • ссылочный возвращаемый тип из operator* -- вместо IList<T> установщика индексатора
  • operator== и operator!= -- нет настоящего эквивалента в .NET, но с контейнером end() соответствует возвращаемому значению IEnumerator<T>::MoveNext()

Для итераторов произвольного доступа:

  • operator+, operator-, operator[] -- вместо IList<T>

Если вы определите их, то стандартные алгоритмы будут работать с вашим контейнером и итератором. Не нужен интерфейс, не нужны виртуальные функции. Неиспользование виртуальных функций делает общий код C++ быстрее, чем эквивалентный код .NET, а иногда и намного быстрее.


Примечание: при написании общих алгоритмов лучше использовать std::begin(container) и std::end(container) вместо функций-членов контейнера. Это позволяет использовать ваш алгоритм с необработанными массивами (у которых нет функций-членов) в дополнение к контейнерам STL. Необработанные массивы и необработанные указатели удовлетворяют всем остальным требованиям контейнеров и итераторов, за этим единственным исключением.

person Ben Voigt    schedule 06.01.2012
comment
Это хорошее объяснение использования эквивалентов IEnumerable, но как насчет их создания? Что, если я хочу определить интерфейс, который предоставляет элемент, по которому я могу выполнять функции begin() и end(), но не заботясь о конкретном типе, который реализует этот член? - person Sander; 09.08.2013
comment
Андрей Александреску с вами не согласен. См. Итераторы должны идти: zao.se/~zao /boostcon/09/2009_presentations/wed/ Диапазоны C++/D являются предлагаемой заменой, и знаете ли вы, что диапазоны почти точно соответствуют интерфейсу .NET IEnumerator. - person naasking; 15.11.2013
comment
@naasking: я не знаю, как это представляет собой разногласие. Теперь у нас есть два способа перебора диапазонов без виртуально диспетчеризированного интерфейса. Ваше заявление о том, что диапазоны равны IEnumerator, свидетельствует о невежестве в отношении того, что такое IEnumerator на самом деле. Диапазоны — это утиные типы, .NET IEnumerable отправляется динамически. И обратите внимание, что диапазоны, несмотря на все их преимущества, по-прежнему не являются каноническим способом работы в стандартном C++. - person Ben Voigt; 15.11.2013
comment
Утиная типизация и динамическая отправка не исключают друг друга. C# 4.0 выполняет утиную типизацию посредством динамической диспетчеризации через полиморфный встроенный кэш. То, что диапазоны С++ не отправляются динамически, не имеет значения. Семантическое соответствие между IEnumerator и диапазонами составляет 1:1 по модулю семантики указателя C++. - person naasking; 15.11.2013
comment
@naasking: C# IEnumerator не имеет утиного типа. Итераторы и диапазоны C++ не имеют виртуальных функций. Утиная типизация актуальна на 100%, потому что это причина, по которой итераторы и диапазоны C++ не должны наследоваться от общего базового класса. О, ключевое слово С# dynamic - это не то же самое, что динамическая диспетчеризация, как предполагает ваш комментарий. Это динамическая привязка. Что, кстати, даже медленнее, чем динамическая диспетчеризация, даже с кешем DLR. - person Ben Voigt; 16.11.2013
comment
Наследование не имеет значения, важна только семантика. Домен и диапазон диапазонов IEnumerator и C++ совпадают 1:1. Это сильный изоморфизм. Динамизм — отвлекающий маневр. Кроме того, ваша терминология не является стандартной. Отправка — это динамическая привязка методов во время выполнения через виртуальную таблицу. Наконец, утиную типизацию также можно реализовать с помощью динамической привязки, поэтому вы противоречите сами себе, говоря, что итераторы C++ не совпадают с IEnumerator из-за диспетчеризации. То, что вы называете утиной типизацией, является специфически синтаксической абстракцией, которая не имеет отношения к семантике. Утиная типизация — это семантическое свойство, а не синтаксическое. - person naasking; 16.11.2013
comment
@naasking: Вы показываете свою неграмотность в C ++, когда говорите, что динамизм - отвлекающий маневр. Динамизм требует затрат во время выполнения, умеренных для виртуальной диспетчеризации (например, IEnumerable) и очень высоких для динамического связывания (это единственная форма утиной типизации, которая есть в C#). При проектировании языка C++ большое внимание уделяется избеганию затрат времени выполнения. Динамизм является семантикой. Пожалуйста, прекратите злоупотреблять термином динамическая отправка — он является синонимом виртуальной отправки, а не динамической привязки. Утиный ввод в C# требует динамического связывания. И, наконец, вопрос о IEnumerable, который не утиный тип. - person Ben Voigt; 14.12.2014
comment
Это хорошее начало, но не могли бы вы привести простой пример того, как построить итератор на C++? -- В C# очень просто просто использовать yield return (на самом деле вам никогда не нужно реализовывать IEnumerable вручную, если только вы не делаете что-то шаткое). Как бы вы сделали эквивалент на C++? - person BrainSlugs83; 31.05.2016
comment
@BrainSlugs83: Есть много примеров нативных итераторов C++. Самый простой - это просто указатель. Нет эквивалента yield return... но это действительно реализация сопрограммы и не имеет ничего общего с доступом к контейнеру. Комитет по стандартам C++ рассматривает несколько различных реализаций сопрограмм; некоторые даже имеют экспериментальные реализации в различных компиляторах. Но в этом отношении нет ничего стандартизированного. - person Ben Voigt; 31.05.2016
comment
Кроме того, вы можете создавать произвольные генераторы и предоставлять им тот же интерфейс, что и итераторам. Взгляните на этот пример: stackoverflow.com/a/1388949/103167 - person Ben Voigt; 31.05.2016
comment
Я думаю, что некоторые люди здесь перепутали IEnumerable/IEnumerator с foreach. IEnumerable не является утиным типом. foreach есть. Наличие обоих (шаблонных итераторов и итераторов времени выполнения) лучше, чем одно или другое. C++ STL в этом случае просто ужасен. Попробуйте создать шаблон в реализации виртуальной функции, и вы увидите проблему. - person Paulo Zemek; 02.02.2018
comment
@PauloZemek: Тем не менее, foreach - это чрезвычайно ограниченная форма утиной печати, недостаточная для очень многих интересных алгоритмов. Например, вы не можете создать двоичный поиск с утиным типом на C#, так как для этого требуются итераторы с произвольным доступом, а foreach выполняет только однопроходную итерацию вперед. Тот факт, что вы не можете виртуально отправить шаблон, не означает, что шаблоны бесполезны... Когда вы заботитесь о производительности, виртуальной отправки в любом случае следует избегать, а когда вам нужен полиморфизм, виртуально отправленные итераторы легко создаются на C++. - person Ben Voigt; 02.02.2018
comment
Но поскольку вы бросили мне вызов, вот шаблон в реализации виртуальной функции, а именно итератор, который разыменовывает умный указатель. Вы можете иметь whatever_container<unique_ptr<Base>> и эффективно перемещаться по контейнеру, в то же время виртуально диспетчеризируя его элементы. - person Ben Voigt; 02.02.2018

Стандартным способом C++ является передача двух итераторов:

template<typename ForwardIterator>
void some_function(ForwardIterator begin, ForwardIterator end)
{
    for (; begin != end; ++begin)
    {
        do_something_with(*begin);
    }
}

Пример кода клиента:

std::vector<int> vec = {2, 3, 5, 7, 11, 13, 17, 19};
some_function(vec.begin(), vec.end());

std::list<int> lst = {2, 3, 5, 7, 11, 13, 17, 19};
some_function(lst.begin(), lst.end());

int arr[] = {2, 3, 5, 7, 11, 13, 17, 19};
some_function(arr + 0, arr + 8);

Ура универсальному программированию!

person fredoverflow    schedule 06.01.2012
comment
более общий способ - использовать бесплатные функции std::begin и std::end - person Abyx; 07.01.2012
comment
@Abyx: клиентский код работает с независимыми типами, которые явно имеют .begin и .end, поэтому здесь нет никаких шансов. В любом случае важна универсальность some_function, и этот выбор в клиентском коде не влияет на его реализацию. - person CB Bailey; 07.01.2012
comment
Это противоположно тому, о чем просят, ИМХО. Это показывает, как в общем случае использовать итератор. -- Как бы вы реализовали реальный итератор? (Использование IEnumerable в C# невероятно просто, вы просто используете yield return — но как сделать то же самое в C++?) - person BrainSlugs83; 31.05.2016

Если строго придерживаться вопроса, то, насколько мне известно, ответ отрицательный. Люди продолжали отвечать, какая замена доступна в C ++, что может быть хорошей информацией, но не ответами, и что ОП, скорее всего, уже знал.

Я совершенно не согласен с тем, что "это не нужно", просто дизайн стандартных библиотек C++ и .NET различен. Основная особенность IEnumerable‹> заключается в том, что он полиморфен и поэтому позволяет вызывающей стороне использовать любой класс, который он хочет (массив, список, набор и т. д.), при этом обеспечивая строгую типизацию во время компиляции, отказоустойчивую даже в библиотечных API. .

Единственная альтернатива в C++ — это шаблоны. Но шаблоны C++ не являются безопасно типизированными дженериками времени выполнения, они в основном являются своего рода макросами. Итак, прежде всего, с шаблонами на C++ вы вынуждены предоставлять весь исходный код шаблона всем, кому нужно использовать ваш шаблон. Более того, если вы сделаете API своей библиотеки шаблонным, вы потеряете возможность гарантировать, что вызов к нему будет скомпилирован, а код не будет автоматически самодокументируемым.

Я полностью сочувствую любому другому программисту, который использует и C#, и C++, и разочарован этим моментом.

Однако в С++ 2X планируется добавить функции, включая диапазоны (которые могут удовлетворить ОП?); а также концепции (которые устраняют слабую/плохую проверку типов шаблонов — недостаток, признанный Бьерном Страуструпом сам) и модули (которые могут помочь или не помочь уменьшить боль от шаблонов только для заголовков).

person J.P.    schedule 16.06.2016
comment
Шаблоны C++ не являются безопасно типизированными дженериками во время выполнения, что невероятно вводит в заблуждение. Они безопасно типизированы (гораздо безопаснее, чем полиморфизм .NET) во время компиляции (что позволяет оптимизировать) дженерики. - person Ben Voigt; 02.11.2016
comment
Позвольте мне перефразировать: шаблоны C++ — это всего лишь макросы. C++, конечно, строго типобезопасен — так же, как и C#. - person J.P.; 15.02.2017
comment
Шаблоны C++ существенно отличаются по поведению от макросов, как от слабой разновидности макросов C, так и от любого сверхмощного макропроцессора, независимого от языка, который вы хотите внедрить. Шаблоны и система типов тесно переплетены. - person Ben Voigt; 15.02.2017
comment
Читая книгу Александреску Язык программирования D, я вижу, что это давняя дискуссия... Я понимаю, почему такой язык, как C#, выбрал гомогенный/полиморфный дженерик, но C++ доволен гетерогенными шаблонами и позволяет создавать специализированные экземпляры (даже если он не сочетается с разделением файлов заголовков и файлов реализации, что является другой историей...) Однородный перевод способствует единообразию, простоте и компактному сгенерированному коду. [...] Напротив, гетерогенный перевод способствует специализации, выразительной силе и скорости сгенерированного кода. - person J.P.; 20.02.2017
comment
Полностью согласен с этим: более того, если вы сделаете свою библиотеку API шаблонной, вы потеряете возможность гарантировать, что вызов к ней будет скомпилирован. - person Paulo Zemek; 02.02.2018

IEnumerable<T> концептуально сильно отличается от vector.

IEnumerable обеспечивает доступ только вперед, только чтение к последовательности объектов, независимо от того, какой контейнер (если он есть) содержит объекты. vector на самом деле является контейнером.

В C++, если вы хотите предоставить доступ к контейнеру, не сообщая подробностей об этом контейнере, соглашение заключается в передаче двух итераторов, представляющих начало и конец контейнера.

Хорошим примером является определение accumulate в C++ STL, которое можно противопоставить IEnumerable‹T›.Aggregate

In C++

   int GetProduct(const vector<int>& v)
   {
         // We don't provide the container, but two iterators
         return std::accumulate(v.begin(), v.end(), 1, multiplies<int>());
   }

In C#

  int GetProduct(IEnumerable<int> v)
  {
        v.Aggregate(1, (l, r) => l*r);
  }
person Andrew Shepherd    schedule 06.01.2012
comment
Этот ответ показывает только потребляющую сторону и не очень полезен. - person BrainSlugs83; 31.05.2016
comment
@BrainSlugs83: Ваши комментарии и отрицательные голоса не очень полезны. Ничто в вопросе не спрашивает о синтаксическом сахаре для реализации итератора с использованием сопрограмм, что и есть С# yield return. Этот вопрос не делает акцент ни на стороне реализации, ни на стороне потребления, он спрашивает о самом интерфейсе итератора, а не об удобных оболочках. Вы навязываете собственное любопытство по поводу реализации, что естественно, но бесполезно. Если вы хотите узнать о деталях реализации, задайте свой вопрос, не перехватывайте этот. - person Ben Voigt; 31.05.2016