可以在 swift 中创建静态分配的数组



我想在 swift 中创建一个结构,该结构具有少量固定数量的值(例如 16 个浮点数)作为实例数据。要求此结构不将这些值存储在堆上,以便结构实例的地址是实例 var 的地址。还要求这些值可以通过下标在结构内部访问,就像数组一样。

在 C 中,你可以简单地这样定义这种东西:

struct Matrix4x4 {
    float elements[16];
    ...
} myMatrix;

使用此代码,sizeof(Matrix4x4) == 64&myMatrix == &myMatrix.elements[0]; 在 swift 中,如果我类比地将 elements 变量定义为类型 [Float] ,矩阵实例只包含一个指向数组的指针,因为Array<Float>实例是存储在堆上的对象。

有没有办法在 swift 中获取实例 var 的静态分配,而不会放弃类似数组的下标访问的便利性和效率?

目前,这在"纯 Swift"中是不可能的。有一个很长的讨论在 Swift-Evolution 邮件列表中,从

  • [快速进化] 提案:连续变量(又名固定大小数组类型)

它要求这样的功能,例如将矩阵结构传递给 C 函数。据我所知,该建议很受欢迎,但没有具体计划截至目前,它未列在目前活跃的 Swift 提案。

C 阵列

float elements[16];

作为包含 16 个组件的元组导入到 Swift 中:

public var elements: (Float, Float, Float, Float, Float, Float, Float, Float, Float, Float, Float, Float, Float, Float, Float, Float)

目前,这似乎是定义具有给定内存布局的固定大小结构的唯一方法。苹果公司的乔·格罗夫(Joe Groff)在[迅捷用户]将 C 语义映射到 Swift

Swift 结构具有未指定的布局。如果你依赖于特定的布局,你应该在 C 中定义结构,并暂时将其导入到 Swift 中。

在后来的讨论中:

你可以保留 C 中定义的结构并将其导入到 Swift 中。Swift 将尊重 C 的布局。

如果矩阵类型是在 C 头文件中定义的(为了简单起见,我使用现在以 2x2 矩阵为例)

// matrix.h:
typedef struct Matrix2x2 {
    float elements[4];
} Matrix2x2;

然后它被导入到 Swift 作为

public struct Matrix2x2 {
    public var elements: (Float, Float, Float, Float)
    public init()
    public init(elements: (Float, Float, Float, Float))
}

如上所述,Swift 保留了 C 内存布局,因此矩阵,其元素和第一个元素都具有相同的地址:

var mat = Matrix2x2(elements: (1, 2, 3, 4))
print(sizeofValue(mat)) // 16
withUnsafePointer(&mat) { print($0) }            // 0x00007fff5fbff808
withUnsafePointer(&mat.elements) { print($0) }   // 0x00007fff5fbff808
withUnsafePointer(&mat.elements.0) { print($0) } // 0x00007fff5fbff808
但是,元组

是不可下标的,如果元组成员具有不同的类型。在快速进化邮件列表中还有另一个讨论

  • [快速进化] 统一元组上的集合类型 [从连续变量中分叉]

将"统一元组"视为集合,这将允许下标。不幸的是,这尚未实施。

有一些方法可以通过索引访问元组成员,例如使用Mirror()withUnsafe(Mutable)Pointer().

这是Swift 3(Xcode 8)的可能解决方案,它似乎运行良好。并且仅涉及很少的开销。"诀窍"是定义 C 函数返回指向元素存储的指针:

// matrix.h:
// Constant pointer to the matrix elements:
__attribute__((swift_name("Matrix2x2.pointerToElements(self:)")))
static inline const float * _Nonnull matrix2x2PointerToElements(const Matrix2x2 * _Nonnull mat)
{
    return mat->elements;
}
// Mutable pointer to the matrix elements:
__attribute__((swift_name("Matrix2x2.pointerToMutableElements(self:)")))
static inline float * _Nonnull pointerToMutableElements(Matrix2x2 * _Nonnull mat)
{
    return mat->elements;
}

我们需要两个变体来使适当的值语义起作用(下标 setter 需要变量,下标 getter 与常量或变量一起工作)。"swift_name"属性使编译器将这些函数作为成员导入Matrix2x2类型的函数,比较

  • SE-0044 作为会员导入

现在我们可以在 Swift 中定义下标方法:

extension Matrix2x2 {
    public subscript(idx: Int) -> Float {
        get {
            precondition(idx >= 0 && idx < 4)
            return pointerToElements()[idx]
        }
        set(newValue) {
            precondition(idx >= 0 && idx < 4)
            pointerToMutableElements()[idx] = newValue
        }
    }
}

一切按预期工作:

// A constant matrix:
let mat = Matrix2x2(elements: (1, 2, 3, 4))
print(mat[0], mat[1], mat[2], mat[3]) // 1.0 2.0 3.0 4.0
// A variable copy:
var mat2 = mat
mat2[0] = 30.0
print(mat2) // Matrix2x2(elements: (30.0, 2.0, 3.0, 4.0))

当然,您也可以定义类似矩阵的下标方法

public subscript(row: Int, col: Int) -> Float

以类似的方式。

正如上面的答案所暗示的那样,您可以使用 withUnsafeMutableBytes()assumingMemoryBound(to:) 的组合将 C 数组视为调用范围内的 swift 数组。

withUnsafeMutableBytes(of: &mymatrix.elements) { rawPtr in
        let floatPtr = rawPtr.baseAddress!.assumingMemoryBound(to: Float.self)
        // Use the floats (with no bounds checking)
        // ...
        for i in 0..<10 {
            floatPtr[i] = 42.0
        }
    }

相关内容

  • 没有找到相关文章

最新更新