【问题标题】:Swift generic virtual method call leads to segfaultSwift 泛型虚方法调用导致段错误
【发布时间】:2014-12-12 03:38:37
【问题描述】:

我是 Swift 新手,所以我可能会做一些愚蠢的事情。如果是这样,太好了:请告诉我在哪里!

在以下代码中,您将看到类Derived 继承自泛型类Base<T>

class Base<T> {
  func method(Int) -> T {
    fatalError("Subclasses must override method.")
  }
}

class Derived<T> : Base<Int> {
  override func method(input:Int) -> Int {
    return input
  }
}

在 Swift 1.1 中,非泛型类不可能从泛型类继承。因此,在这种情况下,Derived 有一个虚拟类型变量。

如果我现在使用这些类:

class Container {
  let item: Base<Int>
  init(item:Base<Int>) {
    self.item = item
  }
  func method(input:Int) -> Int {
    return item.method(input)
  }    
}
let a = Derived<Int>()
let b = Container(item:a)
let test = b.method(42)

代码编译良好,但在调用 Derived&lt;Int&gt;.method 时出现段错误。根据我目前的调试,看起来self 指针在Container.method 中是正确的,但是一旦我们进入Derived&lt;Int&gt;.method 就错了。也许发生了一些堆栈损坏?

对此代码的各种细微更改使其可以正常工作(尽管具有不同的语义)。谁能解释这里发生了什么?在我对 Swift 有更多了解之前,我很犹豫是否建议编译器错误。

【问题讨论】:

    标签: generics inheritance swift virtual-functions


    【解决方案1】:

    您肯定在编译器或运行时中发现了一个错误 - 编写这样的代码应该不可能出现段错误。它应该运行正常,或者编译失败。你甚至不需要容器类,你可以用

    重现错误
    let d: Base = Derived<Int>()
    d.method(2)
    

    但是,即使修复了运行时中的错误,我会说这样做可能不是一个好习惯。使派生类泛型的要求是有原因的,并且使用虚拟模板来绕过它可能不是一个好主意。例如声明let d: Base = Derived&lt;String&gt;()是什么意思?

    如果Base 没有实际的实现并且只是抽象的(或者即使它有,你也许可以将它们排除在外)你最好用一个协议替换基础,也许用一个关联的类型来表示 T :

    protocol P {
        // instead of T, best to call it
        // something with a meaningful name...
        typealias T
    
        func method(input: Int) -> T
    }
    
    class D : P {
        typealias T = Int
    
        func method(input: Int) -> Int {
            return input * 2
        }
    }
    
    class Container<U: P> {
        let item:  U
        init(item: U) {
            self.item = item
        }
        func method(input: Int) -> U.T {
            return item.method(input)
        }
    }
    
    let d = D()
    let c = Container(item: d)
    c.method(2)
    

    这种方法还有一个额外的好处,就是允许 D 成为值类型(即结构体)(如果您希望它是),因为它们不能继承,但它们可以。

    【讨论】:

    • 感谢您的详细回复。您提到“使派生类通用的要求是有原因的”。除了缺少功能之外,您知道原因是什么吗?
    • 我也感谢您对我发布的代码的设计建议。不幸的是,我确实需要在我正在构建的真实代码中利用虚拟方法调用。带有typealias 的协议不能被视为抽象类,而只能被视为类型约束。这就是我重构代码以使用继承的原因,这就是这个错误出现的地方。
    • 我在部分猜测,因为官方文档没有说,但我对泛型派生类为什么需要泛型的解释是:泛型类的大小可以不同 - 即类 @987654327 @ 将是不同的大小,具体取决于 T 是 Int(大小为 8)还是 String(大小为 24)。派生类的大小也可以不同,因为它的基类可以,因此考虑到这一点,需要是通用的。
    • 鉴于您的 Derived 类承诺其基础是 Int,大概您可以使用常规协议而不是具有关联类型的协议,这样您可以将基础重构为协议?
    • 我已经回去看看是否可以再次使用基于协议的版本...闭包可能是一种方法。感谢您的答复。提交给 Apple 的错误报告 :)
    猜你喜欢
    • 2020-09-26
    • 2012-08-04
    • 2020-05-16
    • 2011-05-18
    • 2012-06-14
    • 1970-01-01
    • 1970-01-01
    • 2014-03-27
    • 1970-01-01
    相关资源
    最近更新 更多