【问题标题】:Is there a standard C++ equivalent of IEnumerable<T> in C#?C# 中是否有与 IEnumerable<T> 等效的标准 C++?
【发布时间】:2012-01-06 21:17:20
【问题描述】:

或者如果 T 的 Enumerator 只是列出所有元素,那么使用 vector 是否安全?

【问题讨论】:

  • 有一个可以在 C++ 中使用的等效项,您愿意看一下代码示例吗
  • @DJKRAZE:谢谢!我只是想看看除了使用向量之外是否还有更合适的方法,这可能也允许我们自己在 C++ 中实现 GetEnumerator()。

标签: c# c++ ienumerable


【解决方案1】:

C++ 中不需要它,原因如下:

C# 只支持动态多态。所以要创建一个可重用的算法,你需要一个所有迭代器都将实现的接口。那是IEnumerator&lt;T&gt;IEnumerable&lt;T&gt; 是一个返回迭代器的工厂。

另一方面,C++ 模板支持鸭子类型。这意味着您不需要通过接口约束泛型类型参数来访问成员 - 编译器将按名称查找模板的每个单独实例化的成员。

C++ 容器和迭代器具有隐式接口,相当于.NET IEnumerable&lt;T&gt;IEnumerator&lt;T&gt;ICollection&lt;T&gt;IList&lt;T&gt;,即:

对于容器:

  • iteratorconst_iterator 类型定义
  • begin() 成员函数 -- 满足 IEnumerable&lt;T&gt;::GetEnumerator() 的需要
  • end() 成员函数——而不是 IEnumerator&lt;T&gt;::MoveNext() 返回值

对于前向迭代器:

  • value_typetypedef
  • operator++ -- 而不是 IEnumerator&lt;T&gt;::MoveNext()
  • operator*operator-&gt; -- 而不是 IEnumerator&lt;T&gt;::Current
  • operator* 引用返回类型——而不是IList&lt;T&gt; 索引器设置器
  • operator==operator!= -- 在 .NET 中没有真正的等价物,但容器的 end() 匹配 IEnumerator&lt;T&gt;::MoveNext() 返回值

对于随机访问迭代器:

  • operator+, operator-, operator[] -- 而不是 IList&lt;T&gt;

如果您定义了这些,那么标准算法将适用于您的容器和迭代器。不需要接口,不需要虚函数。不使用虚函数使 C++ 泛型代码比等效的 .NET 代码更快,有时更快。


注意:编写泛型算法时,最好使用std::begin(container)std::end(container),而不是容器成员函数。除了 STL 容器之外,这还允许您的算法与原始数组(没有成员函数)一起使用。原始数组和原始指针满足容器和迭代器的所有其他要求,只有一个例外。

【讨论】:

  • 这是使用 IEnumerable 等价物的一个很好的解释,但是如何生产它们呢?如果我想定义一个暴露成员的接口,我可以对其执行 begin() 和 end() 但不关心实现该成员的特定类型怎么办?
  • Andrei Alexandrescu 不同意你的观点。请参阅“必须使用迭代器”:zao.se/~zao/boostcon/09/2009_presentations/wed/… C++/D 范围是建议的替代品,您不知道吗,范围几乎与 .NET IEnumerator 接口完全匹配。
  • @naasking:我不知道这如何构成“分歧”。现在我们有两种方法可以在没有虚拟分派接口的情况下迭代范围。您声称范围是 IEnumerator 表明对 IEnumerator 的实际含义一无所知。范围是鸭子类型,.NET IEnumerable 是动态调度的。请注意,尽管范围具有所有优势,但仍然不是标准 C++ 中的规范处理方式。
  • @naasking: C# IEnumerator 不是鸭子类型。 C++ 迭代器和范围没有虚函数。 Duck 类型是 100% 相关的,因为它是 C++ 迭代器和范围不需要从公共基类继承的原因。哦,正如您的评论所暗示的,C# dynamic 关键字与动态调度不同。它是动态绑定。顺便说一句,这甚至比动态调度还要慢,即使使用 DLR 缓存也是如此。
  • @naasking:当你说活力是一条红鲱鱼时,你就证明了你的 C++ 文盲。 Dynamism 具有运行时成本,对于虚拟调度(如IEnumerable)来说是适中的,对于动态绑定来说非常高(这是 C# 中唯一的“鸭子类型”形式)。 C++ 的语言设计非常强调避免运行时成本。动态语义。请停止滥用术语动态调度——它是虚拟调度的同义词,而不是动态绑定。 C# 中的鸭子类型需要动态绑定。最后,问题是关于IEnumerable,不是鸭子类型的。
【解决方案2】:

据我所知,如果我们严格地坚持这个问题,答案是否定的。人们一直在回复 C++ 中可用的替代品,这可能是很好的信息但不是答案,而且 OP 很可能已经知道了。

我完全不同意“不需要”,只是 C++ 和 .NET 标准库的设计不同。 IEnumerable 的主要特点是它是多态的,因此它使调用者能够使用他想要的任何类(数组、列表、集合等),同时仍然提供编译时强类型,即使在库 API 中也是安全的.

C++ 中唯一的选择是模板。但是 C++ 模板不是安全类型的运行时泛型,它们基本上是一种宏。因此,首先使用 C++ 中的模板,您必须将整个模板源代码提供给需要使用您的模板的任何人。此外,如果您将库 API 模板化,您将无法保证对其的调用将编译,并且代码不会自动进行自我记录。

我完全同情任何其他同时使用 C# 和 C++ 并且对此感到沮丧的程序员。

但是 C++2X 计划添加包括范围在内的功能(可能满足 OP?);以及概念(解决模板的弱/错误类型检查——缺陷admitted by Bjarne Stroustrup 本人)和模块(可能有助于或可能不会有助于减少仅标题模板带来的痛苦)。

【讨论】:

  • “C++ 模板不是安全类型的运行时泛型”令人难以置信的误导。它们是安全类型的(比 .NET 多态性更安全)编译时(允许优化)泛型。
  • 让我改写一下:C++ 模板只是宏。 C++ 当然是强类型安全的——和 C# 一样。
  • C++ 模板在行为上与宏有很大不同,无论是弱的 C 宏种类,还是您愿意采用的任何超强大的独立于语言的宏处理器。模板和类型系统紧密相连。
  • 阅读 Alexandrescu 的 D 编程语言 我看到这是一个长期存在的争论......我明白为什么像 C# 这样的语言选择同构/多态泛型,但 C++ 对异构感到满意模板并允许专门的实例化(即使它不能与分离头文件和实现文件一起使用,这是另一回事......)“同质翻译有利于统一、简单和紧凑的生成代码。[...]翻译有利于专业化、表达能力和生成代码的速度。”
  • 完全同意这一点:“此外,如果您将库 api 模板化,您将无法保证调用它会编译”。
【解决方案3】:

标准的 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::beginstd::end
  • @Abyx:客户端代码在非依赖类型上运行,这些类型显然具有.begin.end,因此这里没有任何问题。无论如何,some_function 的通用性是最重要的,客户端代码中的选择不会影响其实现。
  • 这与所要求的相反,恕我直言。这显示了如何在一般情况下使用迭代器。 -- 你将如何实现实际的迭代器? (在 C# 中使用 IEnumerable,这非常简单,您只需使用 yield return -- 但如何在 C++ 中进行等效操作?)
【解决方案4】:

IEnumerable&lt;T&gt; 在概念上与vector 非常不同。

IEnumerable 提供对一系列对象的只进只读访问权限,而与保存对象的容器(如果有的话)无关。 vector 实际上就是一个容器本身。

在 C++ 中,如果您想提供对容器的访问而不提供该容器的详细信息,则惯例是传入两个表示容器开始和结束的迭代器。

一个很好的例子是accumulate的C++ STL定义,可以和IEnumerable<T>.Aggregate对比

在 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>());
   }

在 C# 中

  int GetProduct(IEnumerable<int> v)
  {
        v.Aggregate(1, (l, r) => l*r);
  }

【讨论】:

  • 这个答案只显示消费方面,并没有太大帮助。
  • @BrainSlugs83:你的 cmets 和 downvotes 并不是很有帮助。问题中没有任何内容涉及使用协程实现迭代器的语法糖,这正是 C# yield return 的含义。这个问题不强调实现端或消费端,它询问迭代器接口本身,而不是便利包装器。您正在对实施施加自己的好奇心,这很自然,但没有帮助。如果你想了解实现细节,问你自己的问题,不要劫持这个。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-30
  • 2017-01-01
  • 2023-03-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多