gpt4 book ai didi

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

转载 作者:IT王子 更新时间:2023-10-29 05:19:08 30 4
gpt4 key购买 nike

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

protocol SomeProtocol
{
var someProperty: Int { get }
}

实际上,这两个功能有什么区别(不是“一个是通用的,另一个不是”)?它们是否生成不同的代码,它们具有不同的运行时特性?当协议(protocol)或功能变得平凡时,这些差异会改变吗? (因为编译器可能会内联这样的内容)
func generic<T: SomeProtocol>(some: T) -> Int
{
return some.someProperty
}

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

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

最佳答案

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

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

这是protocols not conforming to themselves的结果,因此您不能使用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.特化

this fantastic WWDC talk所述,“现有容器”用于表示协议(protocol)类型的值。

该容器包括:
  • 一个值缓冲区,用于存储值本身,长度为3个字。大于此值的值将被堆分配,并且对该值的引用将存储在值缓冲区中(因为引用的大小仅为1个字)。
  • 指向类型的元数据的指针。该类型的元数据中包括一个指向其值见证表的指针,该表管理存在容器中值的生存期。
  • 一个或多个(对于protocol composition而言)指向给定类型的协议(protocol)见证表的多个指针。这些表跟踪可用于在给定协议(protocol)类型实例上调用的协议(protocol)要求的类型实现。

  • 默认情况下,使用类似的结构以便将值传递到通用占位符类型的参数。
  • 参数存储在一个3字的值缓冲区(可以进行堆分配)中,然后传递给该参数。
  • 对于每个通用占位符,该函数采用元数据指针参数。调用时,用于满足占位符的类型的元类型将传递给此参数。
  • 对于给定占位符上的每个协议(protocol)约束,该函数采用协议(protocol)见证表指针参数。

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

    3.分配协议(protocol)要求

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

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

    尽管这样说,简单的协议(protocol)类型的函数可能能够从内联中受益。在这种情况下,编译器能够消除值缓冲区以及协议(protocol)和值见证表的开销(这可以通过检查在-O构建中发出的SIL看到),从而允许它以与通用功能。但是,与通用特化不同,对于给定的函数,不能保证这种优化(除非您使用 apply the @inline(__always) attribute,但通常最好让编译器来决定)。

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

    4.过载解析

    执行重载解析时,编译器将优先使用协议(protocol)类型的函数,而不是通用函数。
    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比普通的参数更喜欢显式类型的参数(请参见 this Q&A)。

    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
    }

    除了参数之外不包含任何 promise ,并且return必须符合 SomeProtocol。传递和返回的实际具体类型不一定必须相同。

    关于swift - 泛型和协议(protocol)类型的功能参数在实践中有什么区别?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38446487/

    30 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com