C++ 互操作性的支持特性和约束
Swift 支持与 C++ 的双向互操作性。本页介绍当前支持哪些 C++ 互操作性特性。它还讨论了当前 C++ 互操作性支持的局限性。此外,它还列出了与 C++ 互操作性支持相关的一系列约束。
C++ 互操作性是 Swift 的一项积极发展的功能。随着 Swift 社区收集关于在混合 Swift 和 C++ 代码库中实际采用 C++ 互操作性的反馈,其设计和功能的某些方面可能会在 Swift 的未来版本中发生变化。每当 Swift 的新版本更改 C++ 互操作性支持的特性集时,此页面都将进行更新。
平台支持
C++ 互操作性在 Swift 支持的所有平台上均支持开发和部署。
混合语言应用程序的最低部署版本
C++ 互操作性不会对您的应用程序施加额外的部署版本要求,除非您的 Swift 代码使用成为引用类型的 C++ 类或结构体。不使用此类引用类型的应用程序的最低部署版本与 Swift 常规的最低部署版本相同。
从 C++ 导入的引用类型的最低部署版本
下表显示了可以使用成为引用类型的 C++ 类或结构体的混合 Swift 和 C++ 应用程序可以部署的最低操作系统版本。下表中未列出的任何部署平台均自动支持,因此导入的引用类型不会对 Ubuntu 或 CentOS 等平台施加额外的部署版本要求。
运行 Swift 应用程序的平台 | 最低部署版本 |
---|---|
macOS | 13.3 |
iOS | 16.4 |
watchOS | 9.4 |
tvOS | 16.4 |
编译器支持
Swift 5.9 及更高版本支持 C++ 互操作性。
Swift 对双向互操作性的支持依赖于 Swift 编译器生成的头文件,然后希望使用 Swift API 的 C++ 代码可以包含该头文件。此头文件使用仅以下 C++ 编译器支持的 Swift 特定编译器扩展
- Clang(从 LLVM 11 及更高版本开始)
- Apple Clang
使用其他编译器构建的 C++ 代码无法从 C++ 调用 Swift 函数或使用 Swift 类型。
C++ 标准库支持
Swift 编译器在与 C++ 互操作时使用平台的默认 C++ 标准库。下表显示了为特定部署平台构建 Swift 代码时使用的 C++ 标准库
运行 Swift 应用程序的平台 | 默认 C++ 标准库 |
---|---|
Apple 平台 | libc++ |
Ubuntu、CentOS、Amazon Linux | libstdc++ |
Windows | Microsoft C++ 标准库 (msvcprt) |
Swift 目前不支持为支持备用标准库的平台选择备用标准库。例如,即使 libc++ 可用于为 Ubuntu 构建 C++ 代码,您也不能在为 Ubuntu 构建 Swift 代码时使用 libc++。
混合 Swift 和 C++ 代码必须使用相同的 C++ 标准库。
使用 C++ 互操作性需要编译器启用对 C++14 标准或更高版本的支持。Swift 允许您更改它使用的 C++ 标准版本;但是,从 Swift 使用的 C++ 库头文件也必须符合所选的 C++ 标准。当使用双向互操作性时,程序需要使用 C++14 支持进行编译,因为为 Swift 模块生成的 C++ 接口使用 C++14 功能。
自定义 C++ 标准版本
以下是如何设置 Swift 编译器用于互操作性的 C++ 标准版本
- 编译包依赖项时,可以在
Package.swift
中使用Package(...)
中的cxxLanguageStandard
参数自定义 C++ 标准版本。 - 对于 Xcode 目标,您可以从“构建设置”选项卡中的“C++ 语言方言”设置中选择使用的 C++ 标准。
- 如果您使用的是不同的构建系统或直接从命令行调用
swiftc
Swift 编译器,则可以使用-Xcc -std=
选项指定 C++ 标准版本,例如,-Xcc -std=c++20
。
支持的 C++ API
本节介绍 Swift 中支持哪些 C++ API。
Swift 中支持的 C++ 函数
Swift 支持调用大多数非模板化的
- 顶层函数
- 命名空间内的函数
- 成员函数,包括实例成员函数和静态成员函数
- 成为引用类型的 C++ 类型的虚成员函数
- 构造函数
- 运算符
- 算术运算符,例如
operator+
、operator-
、operator*
- 前自增运算符
operator++
表示为func successor() -> Self
- 调用运算符
T operator(Param p)
表示为func callAsFunction(p: Param) -> T
- Bool 字面量转换运算符
operator bool
表示为便捷初始化器Bool(fromCxx:)
- 算术运算符,例如
Swift 尚不支持使用右值引用类型的函数和构造函数。
Swift 支持调用某些 C++ 函数模板。任何在其签名中使用依赖类型或通用引用 (T &&
) 的函数或函数模板在 Swift 中都不可用。任何带有非类型模板参数的函数模板在 Swift 中都不可用。可变参数函数模板在 Swift 中不可用。
返回类型在 Swift 中不受支持或参数类型在 Swift 中不受支持的 C++ 函数在 Swift 中不可用。
如果 C++ 函数的参数具有默认值,则如果满足以下条件,该参数在 Swift 中也将具有默认值
- 该函数不是构造函数
- 该参数在 Swift 中不是
inout
- 该参数不是指针
- 该参数不是 const 引用
Swift 中支持的 C++ 类型
以下 C++ 类型可以在 Swift 中使用
- 原始类型,例如
int
和bool
- 指针
- C++ 引用,不包括右值引用/通用引用参数
- 类型别名,仅当基础类型在 Swift 中受支持时
- 可复制的结构体和类
- 可移动的不可复制的结构体和类
- 枚举,包括作用域枚举 (
enum class
)
在 Swift 中成为值类型的 C++ 类型可以被构造并通过值传递。
在 Swift 中成为引用类型的 C++ 类型不能直接由 Swift 代码构造。它们可以在 Swift 和 C++ 之间自由传递。
在 C++ 命名空间内定义的 C++ 类型在 Swift 中可用。
类和结构体模板在 Swift 中不可直接使用。类或结构体模板的实例化特化在 Swift 中可用。Swift 代码可以通过使用在 C++ 头文件中定义的类型别名来访问模板特化类型。
当 C++ 结构体或类的公共数据成员的类型在 Swift 中受支持时,该数据成员在 Swift 中可用。
Swift 中支持的 C++ 标准库类型
以下 C++ 标准库类型在 Swift 中受支持
std::string
、std::u16string
、std::u32string
std::pair
的特化std::vector
的特化std::map
和std::unordered_map
的特化std::set
、std::multiset
和std::unordered_set
的特化std::optional
的特化std::shared_ptr
的特化std::array
的特化
其他标准库类型,例如 std::unique_ptr
、std::function
和 std::variant
在 Swift 中尚不受支持。
Swift 处理的其他 C++ 特性
C++ 异常
Swift 可以与抛出异常的 C++ 代码互操作。但是,Swift 不支持捕获 C++ 异常。相反,当 C++ 代码未捕获的 C++ 异常到达 Swift 代码时,运行的程序将以致命错误终止。
对于任何未捕获的异常,Swift 的严格程序终止执行目前在 Windows 上使用 Swift 构建的 Swift 代码运行时不受支持。任何在 Windows 上运行的混合语言程序,当 C++ 异常传播穿过 Swift 代码时,都应始终终止,因为程序的堆栈将被展开。任何尝试从此类未捕获的异常中恢复的操作都可能导致程序中出现未定义的行为。
Clang 的可用性属性
使用 Clang 的可用性属性注解的 C++ API 在 Swift 中会接收相同的可用性注解。
支持的 Swift API
本节介绍哪些 Swift API 会在生成的头文件中暴露给 C++。
C++ 支持的 Swift 结构体
Swift 可以为大多数顶层 Swift 结构体生成 C++ 表示形式。以下 Swift 结构体目前尚不支持:
- 不具有任何存储属性的零大小结构体
- 不可复制的结构体
- 具有泛型约束、或具有超过三个泛型参数、或具有可变泛型参数的泛型结构体
Swift 目前不向 C++ 暴露嵌套结构体。
C++ 支持的 Swift 类和 Actor
Swift 可以为大多数顶层 Swift 类和 Actor 生成 C++ 表示形式。以下 Swift 类目前尚不支持:
- 泛型类和 Actor
Swift 目前不向 C++ 暴露嵌套类和 Actor。
C++ 支持的 Swift 枚举
Swift 可以为大多数不具有关联值的顶层 Swift 枚举,以及一些具有关联值的顶层 Swift 枚举生成 C++ 表示形式。以下 Swift 枚举目前尚不支持:
- 不可复制的枚举
- 具有泛型约束、或具有超过三个泛型参数、或具有可变泛型参数的泛型枚举
- 具有超过一个关联值的枚举 case 的枚举
- 间接枚举
此外,枚举的所有关联值的类型都必须可在 C++ 中表示。可表示类型的确切集合在下面描述可表示的参数或返回类型的部分中介绍。
Swift 目前不向 C++ 暴露嵌套枚举。
C++ 支持的 Swift 函数和属性
仅当 Swift 可以在 C++ 中表示其所有参数和返回类型时,任何函数、属性或初始化器才会暴露给 C++。仅当满足以下条件时,参数或返回类型才可以在 C++ 中表示:
- 它是在同一 Swift 模块中定义的受支持的 Swift 结构体/类/枚举。
- 或者,它是未注解为引用类型的 C++ 结构体/类/枚举。
- 或者,它是支持的 Swift 标准库类型之一。
- 如果它是泛型类型,例如
Array
,则其泛型参数必须绑定到此处列出的类型之一。
- 如果它是泛型类型,例如
- 或者,它是指向上面列出的三个受支持类型类别中任何类型的
UnsafePointer
/UnsafeMutablePointer
/Optional<UnsafePointer>
/Optional<UnsafeMutablePointer>
。
具有未在上面列出的参数类型或返回类型的函数或初始化器目前无法在 C++ 中表示。类型未在上面列出的属性目前无法在 C++ 中表示。
此外,以下 Swift 函数、属性和初始化器目前还不能在 C++ 中表示:
- 异步函数/属性
- 会
throw
的函数/属性/初始化器 - 具有泛型约束或可变泛型的泛型函数/属性/初始化器
- 返回不透明类型的函数
- 返回多个值的函数
- 具有
@_alwaysEmitIntoClient
属性的函数/属性/初始化器
支持的 Swift 标准库类型
Swift 能够在 C++ 中表示以下 Swift 标准库类型:
- 原始类型,例如
Bool
、Int
、Float
及其 C 变体,如CInt
- 指针类型,例如
OpaquePointer
、UnsafePointer
、UnsafeMutablePointer
、UnsafeRawPointer
和UnsafeMutableRawPointer
String
类型Array
类型Optional
类型
有关在 C++ 中使用 Swift 类型(如 String
)的更多详细信息,请查看描述如何从 C++ 使用 Swift 标准库类型的部分。
请注意,C++ 目前不支持 Swift 元组。
C++ 支持的原始 Swift 类型列表
下表列出了 Swift 标准库中定义的可以在 C++ 中表示的原始 Swift 类型:
Swift 类型 | 对应的 C++ 类型 |
---|---|
Bool |
bool |
Int |
swift::Int |
UInt |
swift::UInt |
Int8 |
int8_t |
Int16 |
int16_t |
Int32 |
int32_t |
Int64 |
int64_t |
UInt8 |
uint8_t |
UInt16 |
uint16_t |
UInt32 |
uint32_t |
UInt64 |
uint64_t |
Float |
float |
Double |
double |
Float32 |
float |
Float64 |
double |
CBool |
bool |
CChar |
char |
CWideChar |
wchar_t |
CChar16 |
char16_t |
CChar32 |
char32_t |
CSignedChar |
signed char |
CShort |
short |
CInt |
int |
CLong |
long |
CLongLong |
long long |
CUnsignedChar |
unsigned char |
CUnsignedShort |
unsigned short |
CUnsignedInt |
unsigned int |
CUnsignedLong |
unsigned long |
CUnsignedLongLong |
unsigned long long |
CFloat |
float |
CDouble |
double |
约束和限制
Swift 在 C++ 互操作性支持方面存在一些已知的限制。它们目前在 GitHub 上列出。
Swift 包管理器约束
在 Swift 包管理器中启用 C++ 互操作性的 Swift 目标要求其依赖项也启用 C++ 互操作性。Swift GitHub 问题跟踪了此约束的状态
性能约束
Swift 当前对 C++ 容器类型的支持未提供明确的性能保证。最值得注意的是,当在 Swift 的 for-in
循环中使用集合时,Swift 可能会对集合进行深拷贝。
以下问题跟踪了此性能约束的状态
Swift 中使用 C 或 Objective-C API 的现有代码库的兼容性
在 Swift 中导入和使用 C 或 Objective-C API 的现有代码库中启用 C++ 互操作性可能会导致 Swift 中的一些源代码中断。尽管 C++ 在很大程度上与 C 源代码兼容,但在某些情况下 C++ 会有所不同。下面列出了一些可能导致 Swift 在 C++ 模式下无法导入 C 或 Objective-C 头文件的常见差异情况。
- C++ 使用更广泛的关键字集,这些关键字可能与现有的 C 或 Objective-C API 冲突。例如,一个 C 函数,其参数之一名为
class
,就不是一个有效的 C++ 函数。 - 现有的 C 或 Objective-C 头文件可能会尝试在
extern "C"
块内导入另一个 Clang 模块。这在 C++ 模式下是不允许的。
在这种情况下,建议您在 C 或 Objective-C 头文件中修复这些问题。如果这些头文件来自您无法控制的依赖项,则应向销售这些头文件的供应商报告问题。
一些使用平台 C 标准库中函数的现有代码库可能会遇到歧义错误,因为这些函数也在 C++ 标准库中定义。Swift 倾向于使用 C 标准库中的数学函数,如 sin
和 pow
。Swift 不知道的其他一些函数仍然可能导致错误。如果您遇到与平台 C 标准库中函数的歧义相关的此类错误,您可以通过在 Swift 中调用此类函数时使用显式的模块限定符来解决它。