Или безопасно использовать вектор, если перечислитель T просто перечисляет все элементы?
Существует ли стандартный C++ эквивалент IEnumerable‹T› в C#?
Ответы (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. Необработанные массивы и необработанные указатели удовлетворяют всем остальным требованиям контейнеров и итераторов, за этим единственным исключением.
IEnumerator
, свидетельствует о невежестве в отношении того, что такое IEnumerator
на самом деле. Диапазоны — это утиные типы, .NET IEnumerable
отправляется динамически. И обратите внимание, что диапазоны, несмотря на все их преимущества, по-прежнему не являются каноническим способом работы в стандартном C++.
- person Ben Voigt; 15.11.2013
IEnumerator
не имеет утиного типа. Итераторы и диапазоны C++ не имеют виртуальных функций. Утиная типизация актуальна на 100%, потому что это причина, по которой итераторы и диапазоны C++ не должны наследоваться от общего базового класса. О, ключевое слово С# dynamic
- это не то же самое, что динамическая диспетчеризация, как предполагает ваш комментарий. Это динамическая привязка. Что, кстати, даже медленнее, чем динамическая диспетчеризация, даже с кешем DLR.
- person Ben Voigt; 16.11.2013
IEnumerable
) и очень высоких для динамического связывания (это единственная форма утиной типизации, которая есть в C#). При проектировании языка C++ большое внимание уделяется избеганию затрат времени выполнения. Динамизм является семантикой. Пожалуйста, прекратите злоупотреблять термином динамическая отправка — он является синонимом виртуальной отправки, а не динамической привязки. Утиный ввод в C# требует динамического связывания. И, наконец, вопрос о IEnumerable
, который не утиный тип.
- person Ben Voigt; 14.12.2014
yield return
(на самом деле вам никогда не нужно реализовывать IEnumerable вручную, если только вы не делаете что-то шаткое). Как бы вы сделали эквивалент на C++?
- person BrainSlugs83; 31.05.2016
yield return
... но это действительно реализация сопрограммы и не имеет ничего общего с доступом к контейнеру. Комитет по стандартам C++ рассматривает несколько различных реализаций сопрограмм; некоторые даже имеют экспериментальные реализации в различных компиляторах. Но в этом отношении нет ничего стандартизированного.
- person Ben Voigt; 31.05.2016
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);
Ура универсальному программированию!
std::begin
и std::end
- person Abyx; 07.01.2012
.begin
и .end
, поэтому здесь нет никаких шансов. В любом случае важна универсальность some_function
, и этот выбор в клиентском коде не влияет на его реализацию.
- person CB Bailey; 07.01.2012
IEnumerable
в C# невероятно просто, вы просто используете yield return
— но как сделать то же самое в C++?)
- person BrainSlugs83; 31.05.2016
Если строго придерживаться вопроса, то, насколько мне известно, ответ отрицательный. Люди продолжали отвечать, какая замена доступна в C ++, что может быть хорошей информацией, но не ответами, и что ОП, скорее всего, уже знал.
Я совершенно не согласен с тем, что "это не нужно", просто дизайн стандартных библиотек C++ и .NET различен. Основная особенность IEnumerable‹> заключается в том, что он полиморфен и поэтому позволяет вызывающей стороне использовать любой класс, который он хочет (массив, список, набор и т. д.), при этом обеспечивая строгую типизацию во время компиляции, отказоустойчивую даже в библиотечных API. .
Единственная альтернатива в C++ — это шаблоны. Но шаблоны C++ не являются безопасно типизированными дженериками времени выполнения, они в основном являются своего рода макросами. Итак, прежде всего, с шаблонами на C++ вы вынуждены предоставлять весь исходный код шаблона всем, кому нужно использовать ваш шаблон. Более того, если вы сделаете API своей библиотеки шаблонным, вы потеряете возможность гарантировать, что вызов к нему будет скомпилирован, а код не будет автоматически самодокументируемым.
Я полностью сочувствую любому другому программисту, который использует и C#, и C++, и разочарован этим моментом.
Однако в С++ 2X планируется добавить функции, включая диапазоны (которые могут удовлетворить ОП?); а также концепции (которые устраняют слабую/плохую проверку типов шаблонов — недостаток, признанный Бьерном Страуструпом сам) и модули (которые могут помочь или не помочь уменьшить боль от шаблонов только для заголовков).
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);
}
yield return
. Этот вопрос не делает акцент ни на стороне реализации, ни на стороне потребления, он спрашивает о самом интерфейсе итератора, а не об удобных оболочках. Вы навязываете собственное любопытство по поводу реализации, что естественно, но бесполезно. Если вы хотите узнать о деталях реализации, задайте свой вопрос, не перехватывайте этот.
- person Ben Voigt; 31.05.2016