【问题标题】:C# generics vs C++ templates - need a clarification about constraintsC# 泛型与 C++ 模板 - 需要澄清约束
【发布时间】:2010-10-18 23:09:10
【问题描述】:

复制

What are the differences between Generics in C# and Java… and Templates in C++?


大家好,

我是经验丰富的 C++ 程序员,但对 C# 很陌生。

这些约束和泛型是怎么回事?为什么它与在 C++ 中的工作方式不同,因为 C++ 中的约束是隐式的,并且是从您对模板类所做的实例化派生而来的?

为什么微软不让它像在 C++ 中一样工作?

【问题讨论】:

  • 对不起,我不明白你的评论。什么是 Dup?
  • 我认为这个问题正是关于受约束的模板。看起来不像其他问题那样通用。这个问题可以比其他问题的其他答案更深入地使用概念和公理。因此我不会投票赞成关闭。它当然不是完全重复的
  • 我不认为这是重复的。如果您认为是这样,请阅读此问题的答案和“重复”的答案。这个问题侧重于约束,而另一个问题更广泛。
  • 绝对不是骗局,因为 C# 泛型与 Java 泛型有很大不同...

标签: c# c++ generics templates


【解决方案1】:

嗯,总的来说,C++ 模板和 C# 泛型相似 - 与 Java 泛型相比,完全不同,但它们也有很大的不同。与 C# 一样,通过使用反射获得运行时支持,获取描述用于实例化泛型的类型的对象。 C++ 没有反射,它对类型所做的一切都是在编译时完成的。

C# 泛型和 C++ 模板之间的最大区别确实是 C# 泛型具有更好的类型检查功能。它们总是受到约束,因为它们不允许在定义泛型时未声明有效的操作。 C# 的首席设计师提出了隐含约束会增加复杂性的原因。我不太熟悉C#,所以我不能在这里多说。我将讨论 C++ 中的问题以及它们将如何改进,这样人们就不会认为 C++ 的东西都是错的。

在 C++ 中,模板不受约束。如果您执行操作,则在模板定义时暗示该操作将在实例化时成功。 C++ 编译器甚至不需要在语法上检查模板的有效性。如果它包含语法错误,则必须在实例化时诊断该错误。在此之前的任何诊断都是实施的纯粹好处。

这些隐含的约束在短期内对模板设计者来说很容易,因为他们不必关心在模板界面中说明有效的操作。他们将负担放在模板的用户身上——因此用户必须确保他满足所有这些要求。通常情况下,用户尝试看似有效的操作但失败了,编译器会向用户提供数百行关于某些无效语法或未找到名称的错误消息。因为编译器一开始不知道什么约束被违反了,它列出了错误位置周围的所有代码路径部分,甚至所有不重要的细节,用户将有爬过可怕的错误消息文本。

这是一个基本问题,可以通过在模板或泛型的接口处说明类型参数必须具有的属性来解决。据我所知,C# 可以约束参数以实现接口或继承基类。它在类型级别上解决了这个问题。

C++ 委员会早就看到有必要解决这些问题,很快(可能是明年),C++ 也将有办法声明这些明确的约束看时间-下面的机器注释),如下例所示。

template<typename T> requires VariableType<T>
T f(T a, T b) {
    return a + b; 
}

编译器此时会发出错误信号,因为所写的表达式未被要求标记为有效。这首先帮助模板的设计者编写更多正确的代码,因为代码已经在某种程度上进行了类型检查(以及那里可能的情况)。程序员现在可以说明该要求:

template<typename T> requires VariableType<T> && HasPlus<T, T>
T f(T a, T b) {
    return a + b; 
}

现在,它将编译。编译器通过看到T 作为返回类型出现,自动暗示T 是可复制的,因为T 的使用出现在界面中,而不是在模板主体中。其他要求使用要求条款说明。现在,如果用户使用未定义op+ 的类型,他将收到相应的错误消息。

C++1x 将需求与类型分离。以上适用于原始类型以及类。从这个意义上说,它们更灵活,但也相当复杂。规定何时以及何时满足要求的规则很长......您可以使用新规则说以下内容:

template<typename T> requires MyCuteType<T>
void f(T t) { *t = 10; }

然后,使用int 致电f!只需为MyCuteType&lt;int&gt; 编写一个概念图,它就可以教编译器如何取消引用 int。它会在这样的循环中变得非常方便:

for_each(0, 100, doSomething());

由于程序员可以告诉编译器一个 int 如何满足input iterator 的概念,你实际上可以在 C++1x 中编写这样的代码,如果你只编写适当的概念图,这真的不是全部这么难。

好的,到此为止。我希望我能告诉你,限制模板并不是那么糟糕,但实际上更好,因为现在编译器知道模板中类型和对它们的操作之间的关系。我什至还没有写过axioms,这是C++1x'概念中的另一个好东西。请记住,这是未来的东西,它还没有推出,但大约会在 2010 年推出。然后我们将不得不等待一些编译器来实现这一切:)


从“未来”更新

C++0x 概念被草案接受,但在 2009 年底被否决。太糟糕了!但也许我们会在下一个 C++ 版本中再次看到它?让我们充满希望!

【讨论】:

【解决方案2】:

C++ 模板: 编译器检查参数是否满足代码设置的约束。例如:

template <typename T, unsigned int dim>
class math_vector
{
    T elements[dim];

    math_vector<T,dim> operator+ (const math_vector<T,dim>& other) const
    {
        math_vector<T,dim> result;
        for (unsigned int i = 0; i < dim; ++i)
            result.elements[i] = elements[i] + other.elements[i];
    }
}

struct employee
{
    char name[100];
    int age;
    float salary;
}

math_vector<int, 3> int_vec; //legal
math_vector<float, 5> float_vec; //legal
math_vector<employee, 10> employee_vec; //illegal, operator+ not defined for employee

在本例中,您可以创建一个类,为其定义operator+,并将其用作math_vector 的参数。因此,当且仅当模板参数满足模板代码定义的约束时,模板参数才有效。这非常灵活,但会导致编译时间较长(每次实例化模板时都必须检查类型是否满足模板的约束)。

C# 泛型: 不是检查每个特定实例化的有效性,这会导致更长的编译时间并且容易出错,而是明确声明泛型的参数必须实现一个特定的接口(一组方法、属性和运算符)。在泛型的代码中,您不能随意调用任何方法,只能调用该接口支持的方法。每次实例化泛型时,运行时不必检查参数是否满足一长串约束,而只需检查它是否实现了指定的接口。当然,这不太灵活,但也不太容易出错。示例:

class SortedList<T> where T : IComparable<T>
{
    void Add(T i) { /* ... */ }
}

class A : IComparable<A> { /* ... */ }

class B
{
    int CompareTo(B b) { /* ... */ }
    bool Equals(B b) { /* ... */ }
}

SortedList<A> sortedA; // legal
SortedList<B> sortedB; // illegal
// B implements the methods and properties defined in IComparable,
// however, B doesn't explicitly implement IComparable<B>

【讨论】:

    【解决方案3】:

    我敢肯定,您很快就会得到更好的答案。到时候我就删了这个。

    不同之处在于 C++ 中的模板类似于宏。当模板被实例化时,代码被编译,如果违反了隐式约束,则会显示编译错误。这就是您可以进行模板特化的方式 - 模板基本上已经被特化扩展了,所以这就是使用的模板。

    .NET 中的泛型(也在 VB.NET 中)是一个运行时构造。他们是一种特殊的类型。约束是必要的,以确保在最终使用该类型时该类型的任何实际使用都是有效的。

    您实际上可以使用反射来查看泛型类型并找到用于实例化它的类型参数,或者查看泛型定义并查看每个类型参数的约束。在 C++ 中,这些信息在运行时就已经消失了。

    【讨论】:

    • 仅供参考 - C++ 模板与宏不同 - 它们由编译器处理,而不是宏处理器。无论模板是否实例化,编译器都会处理代码。
    • 在 c++ 中,您没有关于实例化模板的反射或元类型信息。但是您可以部分专门化模板并由此获取使用的模板参数,或者使用 typedefs 导出参数(非常常见)。
    • C++ 模板与宏不相似,句号。这只是一种完全不同的机制。
    • 请冷静。在这种情况下,它们“类似于宏”,因为它们的行为类似于编译器编译的文本扩展。 .Net 泛型是“在运行时扩展的”(这就是它们不被称为模板的原因,因为它们与 C++ 模板不同)。
    【解决方案4】:

    C# 泛型与 C++ 完全不同。

    在 C# 中,编译器基本上为所有对象类型编译一个类定义,并为每个值类型编译一个类定义。

    在 C++ 中,每种类型都有自己的类定义。

    约束仅适用于编译器,因此您可以从其他位置推断内容。

    我建议查看Action&lt;T&gt;Func&lt;T, T&gt;Predicate&lt;T&gt; 代表以及相关的IEnumerable 扩展方法。将 lambda 函数与这些函数一起使用,您将看到约束在做什么。

    【讨论】:

    • 我认为这些约束确实是 C++ 模板的一个重要区别。下一个 c++ 版本引入了概念,这也将向 c++ 引入约束模板。每个操作都必须在模板定义时由需求子句声明有效,就像在 C# 中一样,但功能更强大
    • 我的意思是约束没有什么神奇的;它们用于编译器的类型检查,就是这样。
    【解决方案5】:

    模板和泛型确实是不同的东西。 泛型的目标之一是能够以跨库、跨语言的方式使用它们,这与 C++ 模板不同。它们是 CLR 概念,而不是语言概念(尽管它们显然需要语言支持)。

    在 C++ 中,可以以“类固醇上的宏”的方式看到模板(请不要火焰,我知道模板不是宏),因为您可以将它们视为文本扩展,然后编译。这使他们能够使用模板参数上定义的任何内容(例如主要是运算符),因为使用它们的代码会施加约束。

    在 .NET 中,由于泛型是在运行时解析(实例化)的,因此必须在定义级别施加约束,以便编译器可以确保它们的使用是有效的(这意味着您不能对泛型参数使用运算符,因为您不能指示运算符存在的约束)。

    正如我所说,泛型的主要目的是能够创建通用 dll 以供其他项目使用。这就是它们不同的原因。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多