当前位置: 首页 > 面试题库 >

泛型和协议类型的功能参数在实践中有什么区别?

屈晨
2023-03-14
问题内容

给定一个没有任何关联类型的协议:

protocol SomeProtocol
{
    var someProperty: Int { get }
}

实际上,这两个功能有什么区别(意味着不是“一个是通用的,另一个不是”)?它们是否生成不同的代码,它们具有不同的运行时特性?当协议或功能变得平凡时,这些差异会改变吗?(因为编译器可能会内联这样的内容)

func generic<T: SomeProtocol>(some: T) -> Int
{
    return some.someProperty
}

func nonGeneric(some: SomeProtocol) -> Int
{
    return some.someProperty
}

我主要是在询问编译器功能的差异,我了解两者的语言水平含义。基本上,这是否nonGeneric意味着代码大小恒定,但动态分配速度较慢,而不是generic通过传递的每种类型使用不断增长的代码大小,但静态分配速度快?


问题答案:

(我意识到OP很少询问语言的含义,而更多地询问编译器的功能-但我认为列出通用和协议类型的函数参数之间的一般差异也很值得)

1.受协议约束的通用占位符必须满足具体类型

这是协议不符合自身的结果,因此您不能generic(some:)使用SomeProtocol类型化的参数进行调用。

struct Foo : SomeProtocol {
    var someProperty: Int
}

// of course the solution here is to remove the redundant 'SomeProtocol' type annotation
// and let foo be of type Foo, but this problem is applicable anywhere an
// 'anything that conforms to SomeProtocol' typed variable is required.
let foo : SomeProtocol = Foo(someProperty: 42)

generic(some: something) // compiler error: cannot invoke 'generic' with an argument list
                         // of type '(some: SomeProtocol)'

这是由于通用函数预计某些类型的参数T符合SomeProtocol-但是SomeProtocol不是
一个类型符合SomeProtocol

然而,一个非通用的功能,与参数类型的SomeProtocol 接受foo作为参数:

nonGeneric(some: foo) // compiles fine

这是因为它接受“可以键入为”的任何内容SomeProtocol,而不是“符合”的特定类型SomeProtocol

2.专业化

就像在这个梦幻般的WWDC演讲中所涵盖的那样,使用“现有容器”来表示协议类型的值。

该容器包括:

  • 一个值缓冲区,用于存储值本身,长度为3个字。大于此值的值将被堆分配,并且对该值的引用将存储在值缓冲区中(因为引用的大小仅为1个字)。

  • 指向类型的元数据的指针。类型的元数据中包括指向其值见证表的指针,该表管理存在容器中值的生存期。

  • 指向给定类型的协议见证表的一个或多个(在协议组合的情况下)。这些表跟踪可在给定协议类型实例上调用的协议要求的类型实现。

默认情况下,使用类似的结构以便将值传递到通用占位符类型的参数。

  • 参数存储在一个3字的值缓冲区(可以进行堆分配)中,然后将其传递给参数。

  • 对于每个通用占位符,该函数采用元数据指针参数。调用时,用于满足占位符的类型的元类型将传递给此参数。

  • 对于给定占位符上的每个协议约束,该函数采用协议见证表指针参数。

但是,在优化的构建中,Swift能够 专门
化泛型函数的实现–允许编译器为其应用的每种类型的泛型占位符生成一个新函数。这使得参数始终可以按值简单地传递,但代价是增加了代码大小。但是,正如随后所说的那样,积极的编译器优化(尤其是内联)可以抵消这种膨胀。

3.分配协议要求

由于可以对泛型函数进行专门化处理,因此可以静态分配对传入的泛型参数的方法调用(尽管显然不针对使用动态多态性的类型,例如非最终类)。

但是,协议类型的功能通常无法从中受益,因为它们无法从专业化中受益。因此,将通过协议见证表针对该给定参数动态调度对协议类型参数的方法调用,这将更加昂贵。

尽管这样说,简单的协议类型的函数 可能 可以从内联中受益。在这种情况下,编译器 能够消除的值缓冲器和协议和值证人表(这可以通过检查在一个-
O构建发射的SIL中看到)的开销,从而允许其静态调度的方法一样的方法通用功能。但是,与泛型专业化不同,对于给定的函数,不能保证这种优化(除非您应用@inline(__always)属性-但通常最好让编译器来决定这一点)。

因此,一般而言,就性能而言,泛型函数比协议类型的函数更受青睐,因为它们无需嵌入即可实现方法的静态分配。

4.过载解析

执行重载解析时,编译器将优先使用协议类型的函数,而不是通用函数。

struct Foo : SomeProtocol {
    var someProperty: Int
}

func bar<T : SomeProtocol>(_ some: T) {
    print("generic")
}

func bar(_ some: SomeProtocol) {
    print("protocol-typed")
}

bar(Foo(someProperty: 5)) // protocol-typed

这是因为Swift 比普通的参数更喜欢 显式
类型的参数。

5.通用占位符强制使用相同类型

如前所述,使用通用占位符可以使您强制使用该特定占位符键入的所有参数/返回使用相同类型。

功能

func generic<T : SomeProtocol>(a: T, b: T) -> T {
    return a.someProperty < b.someProperty ? b : a
}

接受两个参数,并具有 相同 具体类型的返回值,其中该类型符合SomeProtocol

但是功能:

func nongeneric(a: SomeProtocol, b: SomeProtocol) -> SomeProtocol {
    return a.someProperty < b.someProperty ? b : a
}

除了论据外没有任何承诺,回报必须符合SomeProtocol。传递和返回的实际具体类型不一定必须相同。



 类似资料:
  • 这个问题是在泛型关联类型在Rust中可用之前提出的,尽管它们是被提出和开发的。 我的理解是,特征泛型和关联类型在它们可以绑定到结构的类型数量上有所不同。 关联类型仅绑定1个类型: 泛型关联类型是这两种类型的混合。它们绑定到一个类型,正好有一个关联的生成器,而生成器又可以关联任何数量的类型。那么前面示例中的和这个泛型关联类型有什么区别呢?

  • 问题内容: Java具有泛型,而通过提供了非常强大的编程模型。那么,和Java泛型之间有什么区别? 问题答案: 它们之间有很大的区别。在C ++中,您不必为泛型类型指定类或接口。这就是为什么您可以创建真正的泛型函数和类,而不必担心键入错误。 上面的方法添加了两个相同类型的对象,并且可以用于具有“ +”运算符的任何类型T。 在Java中,如果要在传递的对象上调用方法,则必须指定一种类型,例如: 在C

  • 问题内容: 我正在尝试通过符合协议的编码模型来获取数据。但是它无法像下面的代码那样调用func : 但是在另一个演示中,效果很好,为什么呢? 问题答案: 解决方案1。 https://github.com/satishVekariya/SVCodable 试试这个代码,它扩展了可编码 解决方案2。 避免污染带有扩展名的Apple提供的协议 用

  • 你好打字专家们, 有人可以解释为什么下面的代码在第 16 行给我一个错误,而不是在第 13 行给我一个错误。这是预期功能还是缺少功能? 密码 游乐场链接

  • 问题内容: 我正在使用泛型编写某些东西,令我惊讶的是,我发现这行不通: 那我不能实例化泛型吗?没有任何方法可以做到这一点吗? 问题答案: 是的,这真是令人讨厌。 我使用的解决方法是强制客户端在构造新类时传递类-即 然后您可以使用。

  • 我试图创建一个Java方法,它接受一个对象类型和它应该转换成的数据类型。 例如,如果我应该能够返回一个值1作为Int或双根据需要。我使用类传递数据类型作为参数。 问题:如何使方法泛型以接受基于输入参数的返回类型? 下面的代码只是一个示例,它可能在语法上不正确,用于解释我的问题。