Swift:覆盖自我需求是允许的,但会导致运行时错误.为什么?

Swift:覆盖自我需求是允许的,但会导致运行时错误.为什么?,第1张

概述我刚刚开始学习 Swift(v.2.x),因为我很好奇新功能如何发挥,特别是具有自我需求的协议. 以下示例将正确编译,但会导致任意运行时效应发生: // The protocol with Self requirementprotocol Narcissistic { func getFriend() -> Self}// Base class that adopts the p 我刚刚开始学习 Swift(v.2.x),因为我很好奇新功能如何发挥,特别是具有自我需求的协议.

以下示例将正确编译,但会导致任意运行时效应发生:

// The protocol with Self requirementprotocol Narcissistic {    func getFrIEnd() -> Self}// Base class that adopts the protocolclass Mario : Narcissistic  {    func getFrIEnd() -> Self {        print("Mario.getFrIEnd()")        return self;    }}// Intermediate class that eliminates the// Self requirement by specifying an explicit type// (Why does the compiler allow this?)class SuperMario : Mario {    overrIDe func getFrIEnd() -> SuperMario {        print("SuperMario.getFrIEnd()")        return SuperMario();    }}// Most specific class that defines a fIEld whose// (polymorphic) access will cause the world to explodeclass FireFlowerMario : SuperMario {    let fireballCount = 42    func throwFireballs() {        print("Throwing " + String(fireballCount) + " fireballs!")    }}// Global generic function restricted to the protocolfunc queryFrIEnd<T : Narcissistic>(narcissistic: T) -> T {    return narcissistic.getFrIEnd()}// Sample clIEnt code// Instantiate the most specific classlet m = FireFlowerMario()// The call to the generic function is verifIEd to return// the same type that went in -- 'FireFlowerMario' in this case.// But in reality,the method returns a 'SuperMario' and the// call to 'throwFireballs' will cause arbitrary// things to happen at runtime.queryFrIEnd(m).throwFireballs()

你可以看到在行动on the IBM Swift Sandbox here的例子.
在我的浏览器中,输出如下:

SuperMario.getFrIEnd()Throwing 32 fireballs!

(而不是42!或者说,而不是运行时异常’,因为这个方法甚至没有被定义在被调用的对象上).

这是Swift目前不是类型安全的证明吗?

编辑#1:

这样的不可预测的行为必须是不可接受的.
真正的问题是,关键字Self(资本第一个字母)的确切含义是什么.
我在网上找不到任何东西,但至少有两种可能性:

>自我只是一个完整的类名称的句法快捷方式,它可以替换后者,而不会改变意义.但是,它不能与协议定义中出现的含义相同.
> Self是一种通用/关联类型(在协议和类中),在派生/采用类中被重新实例化.如果是这样,编译器应该拒绝在SuperMario中覆盖getFrIEnd.

也许真正的定义不是那些.如果有更多的语言经验的人会对这个话题有所了解,那将会很好.

是的,似乎有矛盾. Self关键字用作返回类型时,显然意味着“self”作为Self的一个实例.例如,给定这个协议
protocol ReturnsReceived {    /// Returns other.    func doReturn(other: Self) -> Self}

我们无法实现如下

class Return: ReturnsReceived {    func doReturn(other: Return) -> Self {        return other    // Error    }}

因为我们得到一个编译器错误(“不能转换返回类型的返回表达式”返回“返回类型”自我“),如果我们违反doReturn()的合同并返回自己而不是其他的,它将消失.我们不能写

class Return: ReturnsReceived {    func doReturn(other: Return) -> Return {    // Error        return other    }}

因为这只允许在最后一个类中,即使Swift支持协变返回类型. (以下实际编译.)

final class Return: ReturnsReceived {    func doReturn(other: Return) -> Return {        return other    }}

另一方面,正如你所指出的那样,Return的一个子类可以“超越”自我的要求,并快乐地尊重ReturnsReceived的合同,就好像Self是符合类的名字的简单占位符一样.

class SubReturn: Return {    overrIDe func doReturn(other: Return) -> SubReturn {        // Of course this crashes if other is not a        // SubReturn instance,but let's ignore this        // problem for Now.        return other as! SubReturn    }}

我可能是错的,但我认为:

>如果自我作为一个返回类型真的意味着“自我为一个实例
自我“,编译器不应该接受这种自我要求
覆盖,因为它可以返回实例
不是自我除此以外,
>如果Self作为返回类型必须只是一个没有进一步影响的占位符,那么在我们的示例中,编译器应该已经允许在Return类中覆盖Self要求.

也就是说,在这里,关于Self的精确语义的任何选择都不一定会改变,你的代码说明了编译器很容易被愚弄的情况之一,最好的方法是生成代码来延迟检查以运行,时间.在这种情况下,应该委托给运行时的检查与投射有关,在我看来,您的示例揭示的一个有趣的方面是,在特定的位置,Swift似乎不委派任何东西,因此不可避免的崩溃更加戏剧化比它应该是.

Swift能够在运行时检查投票.让我们考虑下面的代码.

let sm = SuperMario()let ffm = sm as! FireFlowerMarioffm.throwFireballs()

在这里,我们创建一个SuperMario并将其下载到FireFlowerMario.这两个类不是无关的,我们正在确保编译器(as!),我们知道我们正在做什么,所以编译器原样保留它,并编译第二行和第三行.然而,程序在运行时失败,抱怨说

Could not cast value of type'SomeModule.SuperMario' (0x...) to'SomeModule.FireFlowerMario' (0x...).

当尝试在第二行的演员.这没有错或令人惊讶的行为.例如,Java将完全相同:编译代码,并在运行时使用ClassCastException失败.重要的是应用程序在运行时可靠地崩溃.

你的代码是一个更加精细的愚弄编译器的方式,但它归结为同样的问题:有一个SuperMario而不是一个FireFlowerMario.不同的是,在你的情况下,我们没有得到一个温和的“无法投射”的消息,但在一个真正的Xcode项目中,一个突然而惊人的错误,当调用throwFireballs().

在同样的情况下,Java在运行时失败(与ClassCastException相同),这意味着在对queryFrIEnd()返回的对象上调用throwFireballs()之前尝试使用一个转换(到FireFlowerMario).在字节码中存在明确的checkcast指令容易证实这一点.

Swift恰恰相反,就目前而言,在调用之前不尝试任何转换(在编译代码中不调用任何转换例程),所以一个可怕的,未被捕获的错误是唯一可能的结果.相反,如果您的代码产生运行时“无法投递”错误消息,或者与此相似的内容,我将完全满意该语言的行为.

总结

以上是内存溢出为你收集整理的Swift:覆盖自我需求是允许的,但会导致运行时错误.为什么?全部内容,希望文章能够帮你解决Swift:覆盖自我需求是允许的,但会导致运行时错误.为什么?所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/1026979.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-23
下一篇 2022-05-23

发表评论

登录后才能评论

评论列表(0条)

保存