Spec-Zone.ru › Swift

Смешивание Swift и C++

Оглавление

  • Введение
  • Обзор
    • Включение взаимодействия с C++
    • Импорт C++ в Swift
    • Создание модуля Clang
    • Работа с импортированными API C++
    • Экспонирование API Swift для C++
    • Гарантии стабильности исходного кода для кодовых баз смешанных языков
  • Использование типов и функций C++ в Swift
    • Вызов функций C++
    • Структуры и классы C++ по умолчанию являются типами значений
    • Создание типов C++ из Swift
    • Доступ к данным членам типа C++
    • Вызов функций-членов C++
      • Константные функции-члены являются nonmutating
      • Константные функции-члены не должны изменять объект
      • Функции-члены, возвращающие ссылки, по умолчанию небезопасны
      • Перегруженные функции-члены
      • Виртуальные функции-члены
      • Статические функции-члены
    • Доступ к унаследованным членам из Swift
    • Использование перечислений C++
    • Использование псевдонимов типов C++
    • Использование шаблонов классов
  • Настройка сопоставления C++ с Swift
    • Переименование API C++ в Swift
    • Сопоставление методов-геттеров и -сеттеров с вычисляемыми свойствами
  • Расширение типов C++ в Swift
    • Соответствие типа C++ протоколу Swift
    • Соответствие шаблона класса протоколу Swift
  • Работа с контейнерами C++
    • Некоторые контейнеры C++ являются коллекциями Swift
      • Ограничения производительности автоматического соответствия коллекций
      • Правила соответствия для коллекций C++ с произвольным доступом
    • Контейнеры C++ могут быть преобразованы в коллекции Swift
      • Правила соответствия для протокола CxxConvertibleToCollection
    • Использование ассоциативных контейнеров C++ в Swift
    • Рекомендуемый подход к использованию контейнеров C++
      • Использование контейнеров C++ в чувствительном к производительности Swift-коде
    • Рекомендации по работе с контейнерами C++ в Swift
      • Не используйте итераторы C++ в Swift
      • Получите контейнеры C++ при вызове Swift-функций
  • Сопоставление типов C++ с типами ссылок Swift
    • Бессмертные типы ссылок
    • Типы общих ссылок
      • Экспонирование типов общих ссылок C++ обратно из Swift
    • Небезопасные типы ссылок
    • Уникальные типы ссылок
  • Использование стандартной библиотеки C++ из Swift
    • Импорт стандартной библиотеки C++
    • Использование std::string
  • Работа со ссылками и типами представлений C++ в Swift
    • Типы C++, рассматриваемые как ссылки или типы представлений в Swift
    • Безопасный доступ к ссылкам с зависимой продолжительностью жизни
    • Использование методов, возвращающих ссылки и представления с независимой продолжительностью жизни
      • Аннотирование методов, возвращающих независимые ссылки или представления
      • Аннотирование структур или классов C++ как самодостаточных
  • Доступ к API Swift из C++
  • Использование типов и функций Swift из C++
    • Вызов функций Swift
    • Использование структур Swift в C++
      • Создание структуры Swift в C++
    • Использование классов Swift в C++
    • Использование перечислений Swift в C++
      • Использование перечислений со значениями-ассоциатами
    • Вызов методов Swift
    • Доступ к свойствам Swift в C++
  • Использование типов стандартной библиотеки Swift из C++
    • Использование строки Swift в C++
    • Использование массива Swift в C++
    • Использование необязательных значений Swift в C++
  • Приложение
    • Список макросов настройки в <swift/bridging>
  • История изменений документа

Введение

Взаимодействие с C++ — новая функция в Swift 5.9. Можно напрямую вызывать большое количество API C++ из Swift, а некоторые API Swift можно использовать из C++.

Этот документ — справочное руководство по смешанному использованию Swift и C++. Он описывает, как API C++ импортируются в Swift, и приводит примеры, показывающие, как различные API C++ можно использовать в Swift. Он также описывает, как API Swift экспонируются для C++, и приводит примеры, показывающие, как экспонированные API Swift можно использовать из C++.


Взаимодействие с C++ — активно развивающаяся функция Swift. В настоящее время она поддерживает взаимодействие между подмножеством функций языка. На странице статуса представлен обзор поддерживаемых функций взаимодействия, а также перечислены существующие ограничения.

В будущих версиях Swift может измениться способ взаимодействия Swift и C++, так как сообщество Swift собирает отзывы от реального применения взаимодействия C++ в смешанных кодовых базах Swift и C++. Пожалуйста, предоставьте свои отзывы на форумах Swift или создайте запрос на GitHub. Будущие изменения в проектировании или функциональности взаимодействия с C++ по умолчанию не повлияют на код в существующих кодовых базах по умолчанию.

Обзор

Этот раздел предоставляет общий обзор того, как смешивать Swift и C++. Вы можете начать с включения взаимодействия с C++ в Swift. Затем вам необходимо понять, как Swift импортирует заголовки C++, и как импортированные типы и функции C++ представляются компилятором Swift. После этого вам нужно изучить последующие разделы, описывающие как использовать импортированные API C++ в Swift. Также необходимо ознакомиться с тем, как API Swift могут быть экспонированы в остальной части вашей кодовой базы C++. Если вас интересует использование API Swift из C++, вам обязательно нужно ознакомиться со следующими разделами, описывающими как использовать экспонированные API Swift в C++.

Включение взаимодействия с C++

Код Swift по умолчанию взаимодействует с API C и Objective-C. Необходимо включить взаимодействие с C++, если вы хотите использовать API C++ из Swift или экспонировать API Swift для C++.

Следующие руководства описывают, как можно включить взаимодействие с C++, работая со специфической системой сборки или IDE:

  • Узнайте, как использовать API C++ из Swift в пакете Swift
  • Узнайте, как смешивать Swift и C++ в проекте Xcode

Другие системы сборки могут включить взаимодействие с C++, передав необходимый флаг компилятору Swift:

  • Узнайте, как включить взаимодействие с C++, вызывая компилятор Swift напрямую

Импортирование C++ в Swift

Заголовочные файлы обычно используются для описания публичного интерфейса библиотеки C++. Они содержат определения типов и шаблонов, а также объявления функций и методов, тела которых часто помещаются в файлы реализации, которые затем компилируются компилятором C++.

Компилятор Swift включает компилятор Clang. Это позволяет Swift импортировать файлы заголовков C++ с помощью модулей Clang. Модули Clang предоставляют более надежную и эффективную семантическую модель заголовков C++ по сравнению с моделью на основе препроцессора прямого включения содержимого файлов заголовков с использованием директивы #include.

В настоящее время Swift не может импортировать модули C++, введённые в стандарт языка C++20.

Создание модуля Clang

Для того, чтобы Swift импортировал модуль Clang, ему необходимо найти файл module.modulemap, который описывает, как набор файлов заголовков C++ отображается на модуль Clang.

Некоторые IDE и системы сборки могут автоматически генерировать файл карты модуля для целевого C++-билда. Swift Package Manager автоматически генерирует файл карты модуля, когда он находит заголовочный файл-оболочку в C++-целевом объекте. Xcode автоматически генерирует файл карты модуля для целевого объекта фреймворка, где карта модуля ссылается на публичные заголовки фреймворка. В других случаях вам может потребоваться создать файл карты модуля вручную.

Рекомендуемый способ ручного создания файла карты модуля — перечислить все файлы заголовков из конкретной C++-цели, которые вы хотите сделать доступными для Swift. Например, предположим, что мы хотим создать файл карты модуля для C++-библиотеки forestLib. Эта библиотека имеет два файла заголовков: forest.h и tree.h. В этом случае мы можем следовать рекомендуемому подходу и создать файл карты модуля, который содержит две директивы header:

module forestLib {
    header "forest.h"
    header "tree.h"

    export *
}

Директива export * — это ещё одно рекомендуемое дополнение к карте модуля. Оно гарантирует, что типы из модулей Clang, импортированные в модуль forestLib, также будут видны в Swift.

Файл карты модуля должен располагаться рядом с файлами заголовков, на которые он ссылается. Например, в библиотеке forestLib файл карты модуля размещается в директории include:

forestLib
├── include
│   ├── forest.h
│   ├── tree.h
│   └── module.modulemap [NEW]
├── forest.cpp
└── tree.cpp

Теперь, когда у forestLib есть файл карты модуля, Swift может его импортировать, когда включено взаимодействие с C++. Для того, чтобы Swift нашёл модуль forestLib, система сборки должна передать флаг пути импорта (-I), указывающий на forestLib/include при вызове компилятора Swift.

Дополнительную информацию о синтаксисе и семантике файлов карт модулей см. в документации по языку файлов карт модулей Clang.

Работа с импортированными API C++

Компилятор Swift представляет импортированные C++ типы и функции с помощью объявлений Swift после импорта модуля Clang. Это позволяет коду Swift использовать типы и функции C++ так, как будто они являются типами и функциями Swift.

Например, Swift может представлять следующие перечисления C++ и следующий класс C++ из библиотеки forestLib:

enum class TreeKind {
  Oak,
  Redwood,
  Willow
};

class Tree {
public:
  Tree(TreeKind kind);
private:
  TreeKind kind;
};

Перечисление Swift используется внутри компилятором Swift для представления TreeKind:

enum TreeKind : Int32 {
  case Oak = 0
  case Redwood = 1
  case Willow = 2
}

Структура Swift используется внутри компилятором Swift для представления Tree:

struct Tree {
  init(_ kind: TreeKind)
}

Такую структуру можно использовать непосредственно в Swift, как и любую другую структуру Swift:

import forestLib

let tree = Tree(.Oak)

Swift использует типы C++ и вызывает функции C++ напрямую, без каких-либо косвенных ссылок или оберток. В приведенном выше примере Swift напрямую вызывает конструктор класса C++ Tree и сохраняет полученный объект непосредственно в переменную tree.

В последующем разделе этого руководства приведены подробности о способе использования импортированных API C++ в Swift.

Экспонирование API Swift для C++

Помимо импорта и использования API C++, компилятор Swift также может экспонировать API Swift из модуля Swift в C++. Это позволяет постепенно интегрировать Swift в существующую базу кода C++.

К API Swift можно получить доступ, включив заголовочный файл, который система сборки генерирует при построении модуля Swift. Некоторые системы сборки автоматически генерируют заголовок. Xcode может автоматически сгенерировать заголовочный файл для фреймворка или целевого приложения. Другие конфигурации сборки могут генерировать заголовок вручную, следуя инструкциям на странице настройки проекта и сборки. Функции Swift могут принимать типы C++ в качестве параметров. При использовании этих API Swift из C++, заголовки типов C++ в сигнатурах функций Swift необходимо включить перед включением сгенерированного заголовочного файла взаимосвязи.

Сгенерированный заголовок использует типы и функции C++ для представления типов и функций Swift. При включенной взаимосвязи с C++, Swift генерирует C++-связывания для всех поддерживаемых общедоступных типов и функций в модуле Swift. Например, следующую функцию Swift можно вызвать из C++:

// Swift module 'forestRenderer'
import forestLib

public func renderTreeToAscii(_ tree: Tree) -> String {
  ...
}

Внутренняя функция C++, которая непосредственно вызывает реализацию renderTreeToAscii, будет присутствовать в заголовке, сгенерированном компилятором Swift для модуля forestRenderer. Ее можно вызвать из кода C++ после включения сгенерированного заголовка в файл C++. Поскольку API Swift относится к типу Tree, определенному в C++, нам необходимо включить Tree.hpp перед сгенерированным заголовком:

#include "Tree.hpp"
#include "forestRenderer-Swift.h"
#include <string>
#include <iostream>
void printTreeArt(const Tree &tree) {
  std::cout << (std::string)forestRenderer::renderTreeToAscii(tree);
}

На странице статуса взаимосвязи с C++ описываются конструкции языка Swift и типы стандартной библиотеки, которые можно экспонировать в C++. Некоторые неподдерживаемые API Swift отображаются в заголовке сгенерированного C++ с пустыми объявлениями unavailable, поэтому при попытке использовать что-то, что не экспонировано в C++, в коде C++ вы получите ошибку.

Гарантии стабильности исходного кода для смешанных языковых баз кода

Способ взаимодействия Swift с C++ все еще развивается. Некоторые изменения в будущих версиях Swift потребуют изменений в исходном коде смешанных баз кода Swift и C++, которые уже приняли взаимосвязь с C++. Однако Swift не будет заставлять вас принимать новые или развитые функции взаимосвязи с C++ при принятии новой версии Swift-инструментария. Для этого будущие версии Swift будут предоставлять несколько версий совместимости взаимосвязи с C++, так же как Swift поддерживает несколько версий совместимости основного языка Swift. Это означает, что проект, использующий текущую совместимую версию взаимосвязи с C++, будет изолирован от любых изменений, внесенных в последующих выпусках, и может переходить к новым версиям совместимости в своем собственном темпе.

Использование типов и функций C++ в Swift

Широкий спектр типов и функций C++ может быть использован непосредственно из Swift. Этот раздел рассматривает основы того, как поддерживаемые типы и функции могут быть использованы из Swift.

Вызов функций C++

Функции C++ из импортированных модулей можно вызывать, используя привычный синтаксис вызова функций из Swift. Например, эта функция C++ доступна в Swift:

void printWelcomeMessage(const std::string &name);

Код Swift может вызывать такую функцию, как если бы это была обычная функция Swift:

printWelcomeMessage("Thomas")

Структуры и классы C++ по умолчанию являются типами значений

Swift отображает структуры и классы C++ в типы значений Swift по умолчанию. Swift считает их типами значений. Это означает, что они всегда копируются при передаче в вашем коде Swift.

Специальные члены типа структуры или класса C++ используются Swift, когда ему нужно выполнить копирование значения или утилизацию значения при выходе из области видимости. Если тип C++ имеет конструктор копирования, Swift будет использовать его при копировании значения такого типа в Swift. И если тип C++ имеет деструктор, Swift будет вызывать деструктор при уничтожении значения Swift такого типа.

Структуры и классы C++ с удаленным конструктором копирования представляются как некопируемые типы Swift (~Copyable). Если тип C++ имеет допустимый конструктор копирования, то сделать его некопируемым в Swift все еще возможно, аннотировав его макросом SWIFT_NONCOPYABLE.

Некоторые типы C++ всегда передаются с помощью указателя или ссылки в C++. Поэтому может быть нелогично отображать их как типы значений в Swift. Эти типы могут быть аннотированы в C++, чтобы указать компилятору Swift отобразить их как ссылки в Swift вместо этого.

Создание типов C++ из Swift

Публичные конструкторы внутри структур и классов C++, которые не являются конструкторами копирования или перемещения, становятся инициализаторами в Swift.

Например, все три конструктора класса C++ Color доступны в Swift:

class Color {
public:
  Color();
  Color(float red, float blue, float green);
  Color(float value);

  ...
  float red, blue, green;
};

Показанные выше конструкторы Color становятся инициализаторами в Swift. Код Swift может вызвать их для создания значения типа Color:

let theEmptiness = Color()
let oceanBlue = Color(0.0, 0.0, 1.0)
let seattleGray = Color(0.7)

Доступ к членам данных типа C++

Публичные члены данных структур и классов C++ становятся свойствами в Swift. Например, члены данных класса Color, показанного выше, можно получить доступ к ним так же, как и к любому другому свойству Swift:

let color: Color = getRandomColor()
print("Today I'm feeling \(color.red) red but also \(color.blue) blue")

Вызов функций-членов C++

Функции-члены внутри структур и классов C++ становятся методами в Swift.

Постоянные функции-члены являются nonmutating nonmutating section" href="#constant-member-functions-are-nonmutating">

Постоянные функции-члены становятся nonmutating методами Swift, тогда как функции-члены без квалификатора const становятся mutating методами Swift. Например, эта функция-член в классе C++ Color считается mutating методом в Swift:

void Color::invert() { ... }

Изменяемые Color значения могут вызывать invert:

var red = Color(1.0, 0.0, 0.0)
red.invert() // red becomes yellow.

Постоянные Color значения не могут вызывать invert.

С другой стороны, эта постоянная функция-член не является mutating методом в Swift:

Color Color::inverted() const { ... }

Поэтому постоянные Color значения могут вызывать inverted:

let darkGray = Color(0.2, 0.2, 0.2)
let veryLightGray = darkGray.inverted()
Постоянные методы-члены не должны изменять объект

Компилятор Swift предполагает, что постоянные методы-члены не изменяют экземпляр, на который указывает this. Нарушение этого предположения методом-членом C++ может привести к тому, что код Swift не заметит изменения экземпляра, на который указывает this, и будет использовать исходное значение этого экземпляра в дальнейшем ходе выполнения кода Swift.

C++ допускает изменение mutable полей в постоянных методах-членах. Постоянные методы-члены в структурах или классах с такими полями все равно становятся nonmutating методами в Swift. Swift не знает, какие постоянные функции изменяют объект, а какие нет, поэтому ради лучшей удобства API Swift все равно предполагает, что такие функции не изменяют объект. Следует избегать вызова постоянных методов-членов, которые изменяют mutable поля из Swift, если они не явно аннотированы макросом SWIFT_MUTATING.

Макрос SWIFT_MUTATING позволяет явно аннотировать постоянные методы-члены, которые изменяют объект. Такие функции затем становятся mutating методами в Swift.

Методы-члены, возвращающие ссылки, по умолчанию небезопасны

Методы-члены, возвращающие ссылки, указатели или определенные структуры/классы, содержащие ссылки или указатели, часто возвращают ссылку, указывающую внутри this, объекта, используемого для вызова функции. Такие методы-члены считаются небезопасными в Swift, так как возвращаемая ссылка не связана с владеющим объектом, который может быть уничтожен, пока ссылка все еще используется. Swift автоматически переименовывает такие методы-члены, чтобы подчеркнуть их небезопасность. Их имя в Swift начинается с двух подчеркиваний и заканчивается Unsafe. Например, следующий метод-член становится методом __getRootTreeUnsafe в Swift:

class Forest {
public:
  const Tree &getRootTree() const { return rootTree; }

  ...
private:
  Tree rootTree;
};

Набор правил, определяющих, какие функции небезопасны, и рекомендуемые рекомендации по безопасному вызову таких методов из Swift, описаны в последующем разделе, описывающем, как безопасно работать со ссылками и типами представлений C++ в Swift.

Перегруженные методы-члены

C++ позволяет перегружать методы-члены на основе их const квалификатора. Например, класс Forest может иметь два getRootTree члена, которые различаются только по const и типу возвращаемого значения:

class Forest {
public:
  const Tree &getRootTree() const { return rootTree; }
  Tree &getRootTree() { return rootTree; }

  ...
private:
  Tree rootTree;
};

Два getRootTree метода-члена становятся методами в Swift. Swift переименовывает метод mutating, чтобы избежать неоднозначности при наличии двух методов с одинаковым именем и аргументами, если уже существует метод nonmutating с тем же именем в Swift. Переименование добавляет суффикс Mutating к имени метода mutating. Это переименование выполняется до учета безопасности метода. В приведённом выше примере два getRootTree метода-члена становятся методами __getRootTreeUnsafe и __getRootTreeMutatingUnsafe в Swift, поскольку они возвращают ссылку, указывающую на объект Forest.

Виртуальные методы-члены

В настоящее время виртуальные методы-члены недоступны в Swift.

Статические методы-члены

Статические методы-члены C++ становятся static методами Swift.

Доступ к унаследованным членам из Swift

Класс или структура C++ становится отдельным типом в Swift. Его отношение к базовым классам C++ не сохраняется в Swift. Swift старается предоставить доступ к членам, унаследованным от базовых классов типа C++, так же как и публичные методы и данные из базового класса C++ становятся методами и свойствами в Swift, как если бы они были определены в самом классе.

Например, следующие два класса C++ становятся двумя различными структурами Swift:

class Plant {
public:
  void water(float amount) { moisture += amount; }
private:
  float moisture = 0.0;
};

class Fern: public Plant {
public:
  void trim();
};

Структура Fern Swift получает дополнительный метод с именем water, который вызывает метод-член water в классе C++ Plant:

struct Plant {
  mutating func water(_ amount: Float)
}

struct Fern {
  init()
  mutating func water(_ amount: Float) // Calls `Plant::water`
  mutating func trim()
}

Точные правила, определяющие, когда члены из наследуемых базовых типов вводятся в тип Swift, представляющий структуру или класс C++, ещё не окончательно определены в Swift 5.9. Следующий вопрос на GitHub отслеживает их окончательное определение в Swift 5.9.

Использование перечислений C++

Сгруппированные перечисления C++ становятся перечислениями Swift с исходными значениями. Все их случаи также отображаются в случаях Swift. Например, следующее перечисление C++ доступно в Swift:

enum class TreeKind {
  Oak,
  Redwood,
  Willow
};

В Swift оно представлено следующим перечислением:

enum TreeKind : Int32 {
  case Oak = 0
  case Redwood = 1
  case Willow = 2
}

Его можно использовать как любое другое enum в Swift:

func isConiferous(treeKind: TreeKind) -> Bool {
  switch treeKind {
    case .Redwood: return true
    default: return false
  }
}

Несгруппированные перечисления C++ становятся структурами Swift. Например, следующее несгруппированное enum становится структурой Swift:

enum MushroomKind {
  Oyster,
  Portobello,
  Button
}

Случаи несгруппированных перечислений C++ становятся переменными вне структуры Swift:

struct MushroomKind : Equatable, RawRepresentable {
    public init(_ rawValue: UInt32)
    public init(rawValue: UInt32)
    public var rawValue: UInt32
}
var Oyster: MushroomKind { get }
var Portobello: MushroomKind { get }
var Button: MushroomKind { get }

Использование псевдонимов типов C++

Объявление C++ using или typedef становится typealias в Swift. Например, следующее объявление using становится типом CustomString в Swift:

using CustomString = std::string;

Использование шаблонов классов

Экземпляризованная специализация шаблона класса или структуры отображается как отдельный тип в Swift. Например, следующий неэкземпляризованный шаблон класса C++ недоступен сам по себе в Swift:

template<class T, class U>
class Fraction {
public:
  T numerator;
  U denominator;

  Fraction(const T &, const U &);
};

Однако экземпляризованная специализация шаблона класса доступна в Swift. Она обрабатывается как обычная структура или класс C++ при отображении в Swift. Например, специализация шаблона Fraction<int, float> становится структурой Swift:

struct Fraction<CInt, Float> {
  var numerator: CInt
  var denominator: Float

  init(_: CInt, _: Float)
}

Функция, возвращающая специализацию, такую как Fraction<int, float>, доступна в Swift:

Fraction<int, float> getMagicNumber();

Такую функцию можно вызвать из Swift, как любую другую функцию Swift:

let magicNum = getMagicNumber()
print(magicNum.numerator, magicNum.denominator)

Псевдоним типа C++ может ссылаться на конкретную специализацию шаблона класса. Например, для построения Fraction<int, float> из Swift вы сначала хотите создать псевдоним типа C++, который ссылается на такую специализацию шаблона:

// Bring `Fraction<int, float>` type to Swift with a C++ `using` declaration.
using MagicFraction = Fraction<int, float>;

Затем вы можете использовать этот псевдоним типа непосредственно из Swift:

let oneEights = MagicFraction(1, 8.0)
print(oneEights.numerator)

Следующий раздел этого документа описывает, как Swift обобщения и расширения протоколов могут использоваться для написания обобщённого кода Swift, который работает со всеми специализациями шаблона класса.

Настройка отображения C++ в Swift

По умолчанию отображение типов и функций C++ в Swift можно изменить, аннотировав конкретную функцию или тип C++ одним из предоставленных макросов настройки. Например, вы можете выбрать другое имя Swift для конкретной функции C++ с помощью макроса SWIFT_NAME.

Заголовок <swift/bridging> определяет макросы настройки, которые можно использовать для аннотации функций и типов C++. Этот заголовок поставляется со Swift-инструментарием.

END_OF_DOCUMENT_MARKER

На платформах Apple и Linux компилятор C++ и компилятор Swift должны автоматически обнаружить этот заголовок. На других платформах, таких как Windows, вам может потребоваться добавить дополнительные флаги пути поиска заголовков (-I) в вызовы компиляторов C++ и Swift, чтобы убедиться, что этот заголовок найден.

В этом разделе описываются всего два из макросов настройки из заголовка <swift/bridging>. Другие макросы настройки и их поведение документированы в последующих разделах данного документа. Полный список всех макросов настройки приведен в приложении в полном списке.

Переименование C++ API в Swift

Макрос SWIFT_NAME предоставляет другое имя для типов и функций C++ в Swift. Типы C++ могут быть переименованы, указав имя Swift-типа внутри макроса SWIFT_NAME. Например, следующий класс C++ переименовывается в структуру CxxLibraryError в Swift:

class Error {
  ...
} SWIFT_NAME("CxxLibraryError");

При переименовании функции необходимо указать имя Swift-функции (включая метки аргументов) внутри макроса SWIFT_NAME. Например, следующая функция C++ переименовывается в send в Swift:

#include <swift/bridging>
void sendCopy(const std::string &) SWIFT_NAME(send(_:));

При вызове такой функции из Swift необходимо использовать новое имя:

send("Hello, this is Swift!")

Сопоставление геттеров и сеттеров с вычисляемыми свойствами

Макрос SWIFT_COMPUTED_PROPERTY сопоставляет функцию-геттер и функцию-сеттер C++ с вычисляемым свойством в Swift. Например, следующая пара геттер-сеттер сопоставляется с одним вычисляемым свойством treeKind в Swift:

#include <swift/bridging>
class Tree {
public:
  TreeKind getKind() const SWIFT_COMPUTED_PROPERTY;
  void setKind(TreeKind kind) SWIFT_COMPUTED_PROPERTY;

  ...
};

Такое свойство может быть изменено в Swift, так как оно имеет сеттер:

func makeNotAConiferousTree(tree: inout Tree) {
  tree.kind = tree.kind == .Redwood ? .Oak : tree.kind
}

И геттер, и сеттер должны работать с одним и тем же базовым типом C++ для успешного преобразования в Swift.

Можно сопоставить только геттер с вычисляемым свойством, сеттер для этого преобразования не требуется.

Расширение типов C++ в Swift

Swift расширения могут добавлять новую функциональность к типам C++ в Swift. Они также могут привести существующий тип C++ в соответствие с Swift-протоколом.

Расширения могут добавить новую функциональность к типу C++, но они не могут переопределять существующую функциональность типа C++.

Приведение типа C++ в соответствие с Swift-протоколом

Соответствие Swift-протоколу можно добавить к типу C++ ретроактивно (после определения типа). Такое соответствие позволяет использовать следующие случаи использования в Swift:

  • Обобщенные Swift-функции и типы ограниченные протоколами могут работать со значением соответствующего C++.
  • Тип протокола может представлять соответствующее значение C++.

Например, Swift-расширение может добавить соответствие Hashable к классу C++ Tree:

extension Tree: Hashable {
  static func == (lhs: Tree, rhs: Tree) -> Bool {
    return lhs.kind == rhs.kind
  }

  func hash(into hasher: inout Hasher) {
    hasher.combine(self.kind.rawValue)
  }
}

Такое соответствие позволяет использовать Tree в качестве ключа в словаре Swift:

let treeEmoji: Dictionary<Tree, String> = [
  Tree(.Oak): "🌳",
  Tree(.Redwood): "🌲"
]

Приведение шаблонного класса в соответствие с Swift-протоколом

Swift-расширение может добавить соответствие протоколу к конкретной специализации шаблона класса в Swift. Например, в Swift доступны специализации, созданные из следующего шаблона класса:

template<class T>
class SerializedValue {
public:
  T deserialize() const;

  ...
};

using SerializedInt = SerializedValue<int>;
using SerializedFloat = SerializedValue<float>;

SerializedInt getSerializedInt();
SerializedFloat getSerializedFloat();

Эти специализации шаблона могут привести себя в соответствие с протоколом, используя Swift extension:

// Swift module 'Serialization'
protocol Deserializable {
  associatedtype ValueType

  func deserialize() -> ValueType
}

// `SerializedInt` specialization now conforms to `Deserializable`
extension SerializedInt: Deserializable {}

В приведенном выше примере SerializedInt соответствует протоколу Deserializable. Однако другие специализации шаблона класса, такие как SerializedFloat, не соответствуют Deserializable.

Макрос SWIFT_CONFORMS_TO_PROTOCOL из заголовка <swift/bridging> может быть использован для автоматического приведения всех специализаций шаблона класса в соответствие с Swift-протоколом. Например, определение шаблона класса SerializedValue может быть прокомментировано с использованием SWIFT_CONFORMS_TO_PROTOCOL:

template<class T>
class SerializedValue {
public:
  using ValueType = T;
  T deserialize() const;

  ...
} SWIFT_CONFORMS_TO_PROTOCOL(Serialization.Deserializable);

Аннотация SWIFT_CONFORMS_TO_PROTOCOL заставляет все специализации, такие как SerializedInt и SerializedFloat, автоматически соответствовать протоколу Deserializable в Swift. Это позволяет добавить функциональность ко всем специализациям шаблона класса в Swift, используя расширение протокола:

extension Deserializable {
  // All specializations of the `SerializedValue` template now have
  // `deserializedDescription` property in Swift.
  var deserializedDescription: String {
    "serialized value \(deserialize().description)"
  }
}

Это также позволяет использовать любую специализацию в ограниченном обобщенном коде без каких-либо дополнительных явных соответствий:

func printDeserialized<T: Deserializable>(_ item: T) {
  print("obtained: \(item.deserializedDescription)")
}

// Both `SerializedInt` and `SerializedFloat` specializations automatically
// conform to `Deserializable`
printDeserialized(getSerializedInt())
printDeserialized(getSerializedFloat())

Работа с C++ контейнерами

Типы контейнеров C++, такие как шаблон класса std::vector, обычно предоставляют основанные на итераторах API для пользователей в C++. Использование C++-итератора небезопасно в Swift, так как такое использование не связано с его содержащим контейнером, который может быть уничтожен, в то время как итератор все еще используется. Вместо того, чтобы полагаться на C++-итераторы, Swift автоматически приводит некоторые типы C++-контейнеров в соответствие с протоколами, которые:

  • Позволяют безопасно получить доступ к базовому контейнеру в Swift с использованием стандартных Swift-API.
  • Предоставляют способ преобразования C++-контейнера в тип Swift-коллекции.

Эти протоколы и правила их соответствия описаны ниже. Рекомендуемый подход к использованию C++-контейнеров, соответствующих этим протоколам, обобщен в последующем разделе.

Некоторые C++-контейнеры являются Swift-коллекциями

Swift приводит в соответствие C++-контейнеры, предоставляющие произвольный доступ к своим элементам, такие как std::vector, к протоколу Swift RandomAccessCollection автоматически. Например, контейнер std::vector, возвращенный этой функцией, автоматически приводится в соответствие с протоколом RandomAccessCollection Swift:

std::vector<Tree> getEnchantedTrees();

Соответствие протоколу RandomAccessCollection позволяет безопасно перебирать элементы контейнера в Swift, используя знакомые управляющие конструкции, такие как цикл for-in. Также доступны методы коллекций, такие как map и filter:

let trees = getEnchantedTrees()

// Traverse through the elements of a C++ vector.
for tree in trees {
  print(tree.kind)
}

// Filter the C++ vector and make a Swift Array that contains only
// the oak trees.
let oakTrees = getEnchantedTrees().filter { $0.kind == .Oak }

Свойство count Swift возвращает количество элементов в такой коллекции. Оператор подстроки Swift может быть использован для доступа к определенному элементу в коллекции, а также. Это позволяет изменять отдельные элементы в C++-контейнере:

var trees = getEnchantedTrees()
for i in 0..<trees.count {
  trees[i].kind = .Oak
}

C++-контейнер, соответствующий протоколу RandomAccessCollection, можно легко преобразовать в тип Swift-коллекции, такой как Array:

let treesArray = Array<Tree>(getEnchantedTrees())

Swift не автоматически преобразует типы C++-контейнеров в типы Swift-коллекций. Любое преобразование из типа C++-контейнера, такого как std::vector, в тип Swift-коллекции, такой как Array, явное в Swift.

Ограничения производительности автоматического соответствия коллекции

В настоящее время Swift не предоставляет явных гарантий производительности при использовании C++-контейнеров, соответствующих протоколу RandomAccessCollection. Swift, скорее всего, сделает глубокую копию контейнера, когда:

  • Контейнер используется в цикле for-in.
  • Контейнер используется с методами, такими как filter и reduce, чья реализация происходит от протокола Swift Sequence.

Это ограничение отслеживается на странице статуса. Несколько стратегий для обхода этого ограничения представлены ниже.

Правила соответствия для коллекций C++ с произвольным доступом

Для того, чтобы тип контейнера C++ автоматически соответствовал типу RandomAccessCollection в Swift, должны быть выполнены следующие два условия:

  • Тип контейнера C++ должен иметь члены-функции begin и end. Обе функции должны быть константными и возвращать один и тот же тип итератора.
  • Тип итератора C++ должен удовлетворять требованию C++ RandomAccessIterator. Его можно продвигать с помощью operator += в C++ и индексировать с помощью operator [] в C++.
  • Тип итератора C++ должен сравниваться с помощью operator ==.

Когда эти условия выполнены, Swift адаптирует структуру Swift, представляющую базовый тип контейнера C++, к протоколу CxxRandomAccessCollection, что добавляет соответствие RandomAccessCollection.

Контейнеры C++ могут быть преобразованы в коллекции Swift

Последовательные типы контейнеров C++, которые не обеспечивают произвольный доступ к своим элементам, автоматически соответствуют протоколу CxxConvertibleToCollection в Swift. Например, контейнер std::set, возвращаемый этой функцией, автоматически соответствует протоколу CxxConvertibleToCollection в Swift:

std::set<int> getWinningNumers();

Соответствие протоколу CxxConvertibleToCollection позволяет легко преобразовать контейнер C++ в тип коллекции Swift, например, в Array или Set. Например, возвращаемый std::set из getWinningNumers может быть преобразован как в Swift Array, так и в Swift Set:

let winners = getWinningNumers()
for number in Array(winners) {
  print(number)
}
let setOfWinners = Set(winners)

Контейнеры C++, которые автоматически соответствуют протоколу CxxRandomAccessCollection, также автоматически соответствуют протоколу CxxConvertibleToCollection.

Правила соответствия для протокола CxxConvertibleToCollection CxxConvertibleToCollection Protocol section" href="#conformance-rules-for-cxxconvertibletocollection-protocol">

Для того, чтобы тип контейнера C++ автоматически соответствовал типу CxxConvertibleToCollection в Swift, должны быть выполнены следующие два условия:

  • Тип контейнера C++ должен иметь члены-функции begin и end. Обе функции должны быть константными и возвращать один и тот же тип итератора.
  • Тип итератора C++ должен удовлетворять требованию C++ InputIterator. Его можно инкрементировать с помощью operator ++ в C++ и обращаться к нему с помощью operator * в C++.
  • Тип итератора C++ должен сравниваться с помощью operator ==.

Использование ассоциативных типов контейнеров C++ в Swift

Ассоциативные типы контейнеров C++, такие как std::map, обеспечивают эффективный доступ к хранимым элементам с использованием ключа поиска. Член-функция find, выполняющая такой поиск, небезопасна в Swift. Вместо использования find, Swift автоматически адаптирует ассоциативные контейнеры из стандартной библиотеки C++ к протоколу CxxDictionary. Такое соответствие позволяет использовать оператор подстановки при работе с ассоциативным контейнером C++ в Swift. Например, возвращаемый std::unordered_map из этой функции автоматически соответствует протоколу CxxDictionary в Swift:

std::unordered_map<std::string, std::string>
getAirportCodeToCityMappings();

Возвращаемое значение std::unordered_map может использоваться как словарь в Swift, при этом оператор подстановки возвращает значение, хранящееся в контейнере, или nil, если такого значения нет:

let mapping = getAirportCodeToCityMappings();
if let dubCity = mapping["DUB"] {
   print(dubCity)
}

Используемый оператор подстановки вызывает метод find контейнера безопасно внутри его реализации.

Ассоциативные контейнеры C++ могут быть преобразованы в тип последовательной коллекции Swift, например, в Array, если необходимо вручную перебрать их элементы в Swift.

Swift не адаптирует пользовательские ассоциативные контейнеры C++ к протоколу CxxDictionary автоматически. Для добавления соответствия CxxDictionary для пользовательского ассоциативного типа контейнера можно использовать вручную написанный Swift extension.

Рекомендуемый подход к использованию контейнеров C++

В данном резюме описан рекомендуемый подход к использованию контейнеров C++ в Swift:

  • Используйте цикл for-in для перебора контейнера C++, соответствующего протоколу RandomAccessCollection.
  • Используйте API коллекций, такие как map или filter, при работе с контейнером C++, соответствующим протоколу RandomAccessCollection.
  • Используйте оператор подстановки для доступа к определенному элементу в контейнере C++, соответствующем протоколу RandomAccessCollection
  • Преобразуйте другие последовательные контейнеры в коллекции Swift, если требуется перебор их элементов или использование таких API коллекций, как map или filter.
  • Используйте оператор подстановки из протокола CxxDictionary при поиске значений в ассоциативном контейнере C++.
Использование контейнеров C++ в чувствительном к производительности коде Swift

Текущий цикл for-in в Swift создает глубокую копию контейнера C++ при переборе его элементов. Вы можете избежать этой копии, используя метод forEach, предоставляемый протоколом CxxConvertibleToCollection. Например, контейнер std::vector<Tree>, возвращаемый из getEnchantedTrees, можно перебрать с помощью метода forEach в Swift:

let trees = getEnchantedTrees()
// Swift should not copy the `trees` std::vector here.
trees.forEach { tree in
  print(tree.kind)
}

Рекомендованные подходы при работе с контейнерами C++ в Swift

Не используйте итераторы C++ в Swift

Как указано в начале этого раздела, использование итераторов C++ небезопасно в Swift. Легко неправильно использовать итераторы C++:

  • легко использовать итератор после уничтожения контейнера C++.
  • легко обратиться к элементу по итератору, выходящему за пределы контейнера.

При работе с контейнерами C++ следует использовать протоколы, такие как CxxRandomAccessCollection, CxxConvertibleToCollection и CxxDictionary, а не полагаться на API итераторов C++.

Функции-члены типов контейнеров C++, возвращающие C++ итераторы, помечены как небезопасные в Swift, так же как и функции-члены, возвращающие ссылки. Другие API C++, такие как функции верхнего уровня, которые принимают или возвращают итераторы, могут быть доступны непосредственно в Swift. Следует избегать использования таких функций в Swift.

Заимствуйте контейнеры C++ при вызове функций Swift

Типы контейнеров C++ становятся значениями в Swift. Это означает, что Swift вызывает конструктор копирования контейнера, который, в свою очередь, копирует все элементы каждый раз, когда создается копия в Swift. Например, Swift скопирует все элементы из std::vector<int>, представленного типом CxxVectorOfInt, в новый vector всякий раз, когда он передается в эту функцию Swift:

func takesVectorType(_ : CxxVectorOfInt) {
  ...
}

let vector = createCxxVectorOfInt()
takesVectorType(vector) // 'vector' is copied here.

Будущие модификаторы владения параметрами, которые будут предоставлены в будущей версии Swift, позволят вам избегать копирования при передаче неизменяемых значений в функции. Изменяемые значения могут передаваться по inout в функцию Swift, что позволяет избежать глубокой копии контейнера C++:

func mutatesVectorType(_ : inout CxxVectorOfInt) {
  ...
}

var vector = createCxxVectorOfInt()
takesVectorType(&vector) // 'vector' is not copied!
END_OF_DOCUMENT_MARKER

Сопоставление типов C++ со ссылками Swift

Компилятор Swift позволяет аннотировать некоторые типы C++ и импортировать их как типы ссылок (или типы class) в Swift. Вопрос о том, должен ли тип C++ импортироваться как тип ссылки, является сложным, и для его решения существуют два основных критерия.

Первый критерий — является ли идентификатор объекта частью «значения» типа. Сравнивание адресов двух объектов — это запрос о том, хранятся ли они в одном и том же месте, или же это определение того, представляют ли они «один и тот же объект» в более глубоком смысле?

Второй критерий — всегда ли объекты класса C++ передаются по ссылке. Передаются ли объекты преимущественно с использованием указателя или типа ссылки, например, сырого указателя (*), ссылки C++ (& или &&) или интеллектуального указателя (например, std::unique_ptr или std::shared_ptr)? При передаче по сырому указателю или ссылке ожидается ли, что память будет стабильной и останется действительной, или же получатели ожидают скопировать объект, если им нужно сохранить значение независимо? Если объекты обычно выделены и остаются по стабильному адресу, даже если этот адрес не является семантически частью «значения» объекта, класс может быть по стилю типа ссылки. Иногда программисту необходимо принять решение.

Первый и самый важный критерий часто не может быть автоматически определен компилятором, просто просмотрев код. Если вы хотите, чтобы компилятор Swift сопоставил тип C++ с типом ссылки Swift, вы должны аннотировать тип C++ одним из следующих макросов настройки из заголовка <swift/bridging>:

  • SWIFT_IMMORTAL_REFERENCE
  • SWIFT_SHARED_REFERENCE
  • SWIFT_UNSAFE_REFERENCE

Бессмертные типы ссылок

Бессмертные типы ссылок не предназначены для индивидуального управления программой. Объекты этих типов выделяются и затем намеренно «утечка памяти» без отслеживания их использования. Иногда эти объекты не являются по-настоящему бессмертными: например, они могут быть выделены в области памяти, с ожиданием, что они будут ссылаться только на другие объекты внутри этой области. Тем не менее, от их индивидуального управления не требуется.

Единственное разумное, что может сделать Swift с бессмертными типами ссылок — импортировать их как неуправляемые классы. Это прекрасно, когда объекты действительно бессмертны. Если объект выделен в области памяти, это небезопасно, но это по существу неизбежный уровень небезопасности, учитывая выбор API C++.

Чтобы указать, что тип C++ является бессмертным типом ссылки, примените атрибут SWIFT_IMMORTAL_REFERENCE. Вот пример применения атрибута SWIFT_IMMORTAL_REFERENCE к типу C++ LoggerSingleton:

class LoggerSingleton {
public:
    LoggerSingleton(const LoggerSingleton &) = delete; // non-copyable

    static LoggerSingleton &getInstance();
    void log(int x);
} SWIFT_IMMORTAL_REFERENCE;

Теперь, когда LoggerSingleton импортирован как тип ссылки в Swift, программист сможет использовать его следующим образом:

let logger = LoggerSingleton.getInstance()
logger.log(123)

Общие типы ссылок

Общие типы ссылок — это типы с подсчетом ссылок, которые передаются по указателю или ссылке в C++. Они обычно используют:

  • собственные операции сохранения и освобождения, которые увеличивают и уменьшают счетчик ссылок, хранящийся внутри объекта.
  • или, неинтрузивный тип общего указателя, например, std::shared_ptr, который может хранить счетчик ссылок вне объекта.

В настоящее время Swift может сопоставить классы или структуры C++, использующие пользовательские операции сохранения и освобождения вместе со счетчиком ссылок, хранящимся внутри объекта, с типом ссылки Swift (который ведет себя как Swift class). Другие типы, которые полагаются на std::shared_ptr для подсчета ссылок, по-прежнему могут использоваться как значения в Swift.

Чтобы указать, что тип C++ является общим типом ссылки, используйте макрос настройки SWIFT_SHARED_REFERENCE. Этот макрос ожидает два аргумента: функцию сохранения и функцию освобождения. Эти функции должны быть глобальными функциями, которые принимают ровно один аргумент и возвращают void. Аргумент должен быть указателем на тип C++ (а не на базовый тип). Swift будет вызывать эти пользовательские функции сохранения и освобождения, где в противном случае он бы сохранял и освобождал Swift классы. Вот пример применения SWIFT_SHARED_REFERENCE к типу C++ SharedObject:

class SharedObject : IntrusiveReferenceCounted<SharedObject> {
public:
    SharedObject(const SharedObject &) = delete; // non-copyable

    static SharedObject* create();
    void doSomething();
} SWIFT_SHARED_REFERENCE(retainSharedObject, releaseSharedObject);

void retainSharedObject(SharedObject *);
void releaseSharedObject(SharedObject *);

Теперь, когда SharedObject импортирован как тип ссылки в Swift, программист сможет использовать его следующим образом:

let object = SharedObject.create()
object.doSomething()
// `object` will be released here.
Возвращение типов C++ общих ссылок из Swift

C++ может вызывать API Swift, которые принимают или возвращают типы C++ общих ссылок. Объекты этих типов всегда создаются со стороны C++, но их ссылки могут передаваться туда и обратно между Swift и C++. В этом разделе описываются соглашения об увеличении и уменьшении счетчиков ссылок при передаче таких ссылок через языковые границы. Рассмотрим следующие API Swift:

public func takeSharedObject(_ x : SharedObject) { ... }

public func returnSharedObject() -> SharedObject { ... }

В случае функции takeSharedObject компилятор автоматически вставит вызовы сохранения и освобождения для x по мере необходимости, чтобы удовлетворить семантику соглашений о вызовах с собственностью/гарантиями. Звонящие C++ должны гарантировать, что x существует в течение всего вызова. Обратите внимание, что функции, возвращающие тип общей ссылки, такой как returnSharedObject, передают владение вызывающему объекту. Вызывающий C++ обязан освободить объект.

Небезопасные типы ссылок

Макрос аннотации SWIFT_UNSAFE_REFERENCE имеет тот же эффект, что и макрос аннотации SWIFT_IMMORTAL_REFERENCE. Однако он сообщает о других семантике: тип предназначен для небезопасного использования, а не для существования на протяжении всей программы.

Уникальные типы ссылок

Уникальные типы ссылок, такие как типы, передаваемые с помощью std::unique_ptr, пока не поддерживаются Swift.

Использование C++ стандартной библиотеки из Swift

Импортирование C++ стандартной библиотеки

Swift может импортировать платформенную C++ стандартную библиотеку, импортируя модуль CxxStdlib. Пространство имен std превращается в перечисление std в Swift. Функции и типы внутри пространства имен std становятся вложенными типами и статическими функциями в перечислении Swift std.

На странице состояния содержится список поддерживаемых C++ стандартных библиотек, в котором описываются поддерживаемые C++ стандартные библиотеки на платформах, поддерживаемых Swift.

Использование std::string std::string section" href="#using-stdstring">

Тип C++ std::string становится структурой в Swift. Он соответствует протоколу ExpressibleByStringLiteral, поэтому его можно инициализировать непосредственно с помощью строкового литерала в Swift:

import CxxStdlib

let s: std.string = "Hello C++ world!"

Swift String можно легко преобразовать в C++ std::string:

let swiftString = "This is " + "a Swift string"
let cxxString = std.string(swiftString)

То же преобразование можно выполнить в обратном направлении, переходя от C++ std::string к Swift String:

let cxxString = std.string("This is a C++ string")
let swiftString = String(cxxString)

Swift не преобразует тип C++ std::string в тип Swift String автоматически.

Работа с ссылками и типами представлений C++ в Swift

Как указано ранее, функции-члены, возвращающие ссылки, указатели или определённые структуры/классы, содержащие ссылки или указатели, считаются небезопасными в Swift. Такие функции-члены часто возвращают ссылки или типы представлений, которые указывают внутрь объекта this или в память, принадлежащую объекту this. В таких случаях, срок жизни объекта, на который указывает возвращённая ссылка или представление, считается зависимым от срока жизни его владеющего объекта (объекта this, переданного функции-члену). В настоящее время C++ не определяет, какие функции-члены возвращают зависимые ссылки или представления, а какие — полностью независимые ссылки или представления. Поэтому Swift предполагает, что любая ссылка или любой тип представления, возвращаемый функцией-членом, зависит от объекта this.

Зависимые ссылки и типы представлений небезопасны в Swift, так как ссылка или представление не связаны со своим владеющим объектом. Таким образом, владеющий объект может быть уничтожен, в то время как ссылка всё ещё используется. Из-за этой небезопасности и предположения, что все такие ссылки и представления являются зависимыми, Swift переименовывает такие функции-члены, чтобы подчеркнуть их небезопасность и отговорить от их использования в Swift.

В этом разделе описаны точные правила, используемые Swift для определения функций-членов, которые возвращают зависимые ссылки или типы представлений, и предлагаются подходы к написанию оберток Swift, которые вызывают такие функции-члены безопасно из Swift. Также представлены две новые макрокоманды кастомизации, которые могут быть применены к коду C++, чтобы указать Swift на то, что некоторые функции-члены, которые он считает небезопасными, являются безопасными вместо этого.

Типы C++ , рассматриваемые Swift как ссылки или типы представлений

Swift предполагает, что функция-член C++, возвращающая один из следующих типов, небезопасна в Swift:

  • Ссылка C++
  • Сырой указатель
  • Класс или структура C++ без пользовательского конструктора копирования, содержащая поле, тип которого находится в этом списке, рекурсивно.

Например, следующие две структуры C++ являются типами представлений с точки зрения Swift:

struct PairIntRefs {
  int &firstValue;
  const int &secondImmutableValue;

  PairIntRefs(int &, const int &);
};

// Also a view type, since its `refs` field is a view type.
struct BagOfValues {
  PairIntRefs refs;
  int x;

  BagOfValues(PairIntRefs, int);
};

Описанные выше правила определяют эвристику, которую Swift использует для обнаружения функций-членов, которые, вероятно, возвращают зависимую ссылку или тип представления. Эта эвристика не гарантирует, что все функции-члены C++, возвращающие зависимые ссылки или представления, будут обнаружены Swift, и поэтому некоторые функции-члены, возвращающие зависимые ссылки или представления, могут показаться безопасными в Swift.

Безопасный доступ к ссылкам с зависимым сроком жизни

Рекомендуемый подход к вызову функций-членов, возвращающих ссылку или тип представления с зависимым сроком жизни, заключается в их обертывании в API Swift, который возвращает копию ссылаемого объекта.

Например, рассмотрим класс Forest, члены getRootTree которого возвращают ссылки:

class Forest {
public:
  const Tree &getRootTree() const { return rootTree; }
  Tree &getRootTree() { return rootTree; }

  ...
private:
  Tree rootTree;
};

Как указано ранее, обе эти функции-члены становятся методами __getRootTreeUnsafe и __getRootTreeMutatingUnsafe в Swift. Такие методы не предназначены для непосредственного вызова в вашем коде. Вместо этого вы должны написать обёртку, которая достигает желаемого результата без раскрытия зависимой ссылки, которую затем можно использовать во всём вашем коде Swift. Например, предположим, что вы хотите проверить значение rootTree в Swift. Вы можете добавить обёртку, которая позволит вашему коду Swift проверять rootTree, расширив класс Forest в Swift и добавив вычисляемое свойство rootTree, которое возвращает значение Tree:

import forestLib

extension Forest {
  private borrowing func getRootTreeCopy() -> Tree {
    return __getRootTreeUnsafe().pointee
  }

  var rootTree: Tree {
    getRootTreeCopy()
  }
}

Модификатор владения borrowing, используемый выше, является новым дополнением в Swift 5.9. Некоторые тестовые версии Swift 5.9 могут не позволить вам использовать borrowing для копируемых типов C++, таких как Forest. В таких случаях, до выпуска Swift 5.9, вы можете использовать цепочку вызовов метода mutating, чтобы безопасно скопировать возвращённое Tree методом getRootTree:

import forestLib

extension Forest {
  private mutating func getRootTreeCopy() -> Tree {
    return __getRootTreeUnsafeMutating().pointee
  }

  var rootTree: Tree {
    var mutCopy = self
    return mutCopy.getRootTreeCopy()
  }
}

Использование методов, возвращающих ссылки и представления с независимым сроком жизни

Некоторые функции-члены C++, возвращающие ссылку или тип представления, могут возвращать ссылку, срок жизни которой не зависит от объекта this. Swift по-прежнему будет предполагать, что такие функции-члены небезопасны. Чтобы изменить это, вы можете добавить аннотации к своему коду C++, чтобы указать Swift:

  • Предполагать, что конкретная функция-член C++ возвращает полностью независимую ссылку или представление. Такая функция-член затем будет считаться безопасной.
  • Предполагать, что конкретный класс или структура C++ является самодостаточным. Все функции-члены, возвращающие такие самодостаточные типы, затем будут считаться безопасными.
Аннотирование методов, возвращающих независимые ссылки или представления

Макрокоманда кастомизации SWIFT_RETURNS_INDEPENDENT_VALUE из заголовка <swift/bridging> может быть добавлена к функциям-членам C++, чтобы Swift знал, что она не возвращает зависимую ссылку или зависимое представление. Такая функция-член затем будет считаться безопасной в Swift.

Например, функция-член getName в классе C++ NatureLibrary является прекрасным кандидатом для SWIFT_RETURNS_INDEPENDENT_VALUE, так как её определение чётко показывает, что она возвращает указатель на константную статическую строковую литерал, которая не хранится внутри самого объекта NatureLibrary:

class NatureLibrary {
public:
  const char *getName() const SWIFT_RETURNS_INDEPENDENT_VALUE {
    return "NatureLibrary";
  }
};
Аннотирование структур или классов C++ как самодостаточных

Макрокоманда кастомизации SWIFT_SELF_CONTAINED из заголовка <swift/bridging> может быть добавлена к структуре или классу C++, чтобы Swift знал, что это не тип представления. Все функции-члены, возвращающие такой самодостаточный тип, будут считаться безопасными в Swift.

Доступ к Swift API из C++

Компилятор Swift может сгенерировать заголовочный файл, содержащий типы и функции C++, которые представляют типы и функции Swift, определённые в модуле Swift. Этот заголовочный файл можно включить из кода C++, что позволит вам использовать типы Swift и вызывать функции Swift из C++.

Swift рассматривает все публичные типы и функции, определённые в модуле Swift, как пригодные для экспорта в C++ при генерации сгенерированного заголовочного файла. Однако не все публичные типы и функции могут быть представлены в C++. Точные правила, которые определяют, какие типы и функции Swift в настоящее время экспортируются в C++ в сгенерированном заголовке, описаны в разделе статуса страницы.

Использование типов и функций Swift из C++

Широкий спектр типов и функций Swift экспортируется в C++. В этом разделе рассматриваются основы того, как использовать экспортированные типы и функции Swift из C++.

Вызов функций Swift

Функции Swift верхнего уровня, которые экспортируются в C++, становятся функциями C++ inline в сгенерированном заголовке. Функции C++ размещаются в C++ namespace, который представляет модуль Swift. Тело такой функции C++ напрямую вызывает родную функцию Swift из C++, без использования каких-либо косвенных вызовов.

Например, следующая функция Swift экспортируется в C++ в сгенерированном заголовке:

// Swift module 'Greeter'

public func printWelcomeMessage(_ name: String) {
  print("Welcome \(name)")
}

Код C++ может вызвать printWelcomeMessage после включения сгенерированного заголовка:

#include <Greeter-Swift.h>
void cPlusPlusCallsSwift() {
  Greeter::printWelcomeMessage("Theo");
}

Использование структур Swift в C++

Структуры Swift, которые экспортируются в C++, становятся окончательными классами C++ в сгенерированном заголовке. Структуры верхнего уровня помещаются в C++ namespace, который представляет модуль Swift. Экспортированные инициализаторы, методы и свойства, определённые внутри структуры Swift, становятся членами класса C++.

Класс C++, который представляет структуру Swift, копируется. Его конструктор копирования копирует базовое значение Swift в новое значение. Деструктор класса C++ уничтожает базовое значение Swift.

END_OF_DOCUMENT_MARKER

В настоящее время классы C++, представляющие структуры Swift, не могут быть перемещены в C++ с помощью std::move.

Создание структуры Swift в C++

Раскрытые инициализаторы структуры Swift преобразуются в статические init член-функции класса C++. Код C++ может затем вызвать одну из таких функций для создания экземпляра структуры в C++.

Например, Swift раскрывает структуру MountainPeak, показанную ниже, в сгенерированном заголовке:

// Swift module 'Landscape'

public struct MountainPeak {
  let name: String
  let height: Float

  public init(name: String, height: Float) {
    self.name = name
    self.height = height
  }
}

Статическую член-функцию init из класса C++ MountainPeak можно использовать для создания экземпляра MountainPeak в C++:

#include <Landscape-Swift.h>using namespace Landscape;

void createMountainRange() {
  auto tallestMountain = MountainPeak::init("Everest", 8848.9);
}

Использование классов Swift в C++

Классы Swift, доступные для C++, становятся классами C++ в сгенерированном заголовке. Классы верхнего уровня размещаются в C++ namespace, который представляет модуль Swift. Раскрытые инициализаторы, методы и свойства, определенные внутри класса Swift, становятся членами класса C++.

Класс C++, представляющий класс Swift, копируется и перемещается. Его конструкторы копирования и перемещения, а также деструктор подчиняются правилам модели автоматического управления ссылками (ARC) Swift, что позволяет программе освободить экземпляр класса Swift, когда на него больше нет ссылок.

Например, Swift раскрывает класс MountainRange, показанный ниже, в сгенерированном заголовке:

// Swift module 'Landscape'

public class MountainRange {
  let peaks: [MountainPeak]

  public init(peaks: [MountainPeak]) {
    self.peaks = peaks
  }
}

public func createSierras() -> MountainRange {
  ...
}

public func render(mountainRange: MountainRange) {
  ...
}

Экземпляр MountainRange затем можно безопасно передавать в C++. ARC освободит его, когда он больше не используется:

#include <Landscape-Swift.h>using namespace Landscape;

void renderSierras() {
  MountainRange range = createSierras();
  render(range);
  // The `MountainRange` instance that `range` points to is freed by ARC when
  // this C++ function returns.
}

Иерархия наследования классов Swift представлена иерархией наследования C++, образованной классами C++, представляющими раскрытые классы Swift.

Использование перечислений Swift в C++

Перечисления Swift, доступные для C++, становятся классами C++ в сгенерированном заголовке. Перечисления верхнего уровня размещаются в C++ namespace, который представляет модуль Swift. Раскрытые инициализаторы, методы и свойства, определенные внутри перечисления Swift, становятся членами класса C++.

Класс C++, представляющий перечисление Swift, копируется. Его конструктор копирования копирует базовое значение Swift в новое значение. Деструктор класса C++ уничтожает базовое значение Swift. В настоящее время классы C++, представляющие перечисления Swift, не могут быть перемещены в C++ с помощью std::move.

Случаи перечисления становятся static inline константными членами данных C++ в классе C++, представляющем перечисление. Эти члены позволяют:

  • Создать перечисление Swift, установленное на конкретное значение случая, в C++.
  • Переключаться на перечисление Swift с помощью оператора switch в C++.

Например, следующее перечисление Swift раскрывается в сгенерированном заголовке:

// Swift module 'Landscape'

public enum VolcanoStatus {
  case dormant
  case active
}

Экземпляр VolcanoStatus может быть создан из C++, используя operator() для одного из его членов, представляющего случай перечисления. Вы также можете сослаться на такой член в условии case оператора switch в C++:

#include <Landscape-Swift.h>using namespace Landscape;

VolcanoStatus invertVolcanoStatus(VolcanoStatus status) {
  switch (status) {
  case VolcanoStatus::dormant:
    return VolcanoStatus::active(); // Returns `VolcanoStatus.active` case.
  case VolcanoStatus::active:
    return VolcanoStatus::dormant(); // Returns `VolcanoStatus.dormant` case.
  }
}

Член unknownDefault C++ позволяет написать исчерпывающее C++ switch для устойчивого перечисления Swift, так как такое перечисление может получить больше случаев в будущем, о которых код C++ не знает.

Использование перечислений со связанными значениями

Swift позволяет перечислению связывать набор значений с конкретным случаем. Перечисления, случаи которых имеют одно связанное значение или не имеют связанных значений, раскрываются для C++. Они становятся классами C++ в сгенерированном заголовке. Интерфейс такого класса C++ очень похож на интерфейс класса, сгенерированного для перечисления Swift без связанных значений. Такие классы также содержат дополнительные член-функции-геттеры, которые позволяют извлечь связанное значение, хранящееся в перечислении, после того как вы определите, в каком случае установлено перечисление.

Например, следующее перечисление Swift со связанными значениями раскрывается в сгенерированном заголовке:

// Swift module 'Landscape'

public enum LandmarkIdentifier {
  case name(String)
  case id(Int)
}

Значение, связанное с одним из случаев LandmarkIdentifier, может быть извлечено, вызвав соответствующий метод-геттер в C++:

#include <Landscape-Swift.h>
#include <iostream>using namespace Landscape;

void printLandmarkIdentifier(LandmarkIdentifier identifier) {
  switch (status) {
  case LandmarkIdentifier::name:
    std::cout << (std::string)identifier.getName();
    break;
  case LandmarkIdentifier::id:
    std::cout << "unnamed landmark #" << identifier.getId();
    break;
  }
}

Новый экземпляр LandmarkIdentifier также может быть создан из C++:

auto newLandmarkId = LandmarkIdentifier::id(1234);

Вызов методов Swift

Методы Swift становятся член-функциями в C++.

У структур и перечислений Swift есть mutating и nonmutating методы. Nonmutating методы становятся константными член-функциями в C++.

Доступ к свойствам Swift в C++

Как хранимые, так и вычисляемые свойства становятся методами-геттерами и методами-сеттерами в C++. Геттер — это константная член-функция, возвращающая значение свойства Swift. Мутабельные свойства также имеют сеттер в C++. Сеттер — это член-функция, и его нельзя вызывать для неизменяемых экземпляров типа значения Swift.

Например, следующая структура Swift раскрывается для C++ в сгенерированном заголовке:

public struct LandmarkLocation {
  public var latitude: Float
  public var longitude: Float
}

Код C++ может затем вызвать член-функции getLatitude и getLongitude для доступа к значениям хранимых свойств.

Использование типов стандартной библиотеки Swift из C++

Несколько типов стандартной библиотеки Swift могут использоваться в C++. В этом разделе описывается, как использовать поддерживаемые типы стандартной библиотеки Swift в C++.

Использование типа Swift String в C++ String in C++ section" href="#using-swift-string-in-c">

Тип String Swift раскрывается для C++. Его можно инициализировать непосредственно с помощью строковой литералы в C++:

#include <SwiftLibrary-Swift.h>
void createSwiftString() {
  swift::String test = "Hello Swift world!";
}

C++ std::string легко преобразуется в Swift String:

void callSwiftAPI(const std::string &stringValue) {
  SwiftLibrary::functionTakesString(swift::String(stringValue));
}

То же преобразование можно выполнить в обратном направлении, переходя от Swift String к C++ std::string:

std::string getStringFromSwift() {
  return (std::string)SwiftLibrary::giveMeASwiftString();
}

Представление C++ для String предоставляет доступ к ряду методов и свойств String, включая:

  • isEmpty
  • getCount
  • lowercased и uppercased
  • hasPrefix и hasSuffix
  • append

Доступны и другие методы и свойства, не указанные здесь.

Объект-C NSString * можно преобразовать в Swift String и обратно в режиме Objective-C++.

Использование Swift Array в C++ Array in C++ section" href="#using-swift-array-in-c">

Тип Array Swift раскрывается для C++. C++ представляет его с помощью шаблона класса swift::Array. Он должен быть инстанцирован с типом C++, представляющим тип Swift. Также он может быть инстанцирован с родным типом C++, если Swift Array такого типа используется в Swift и раскрывается для C++ через публичный API Swift.

Swift Array можно перебирать с помощью цикла for в C++. Например, рассмотрим интерфейс модуля Swift StringsAndNumbers:

// Swift module 'StringsAndNumbers'

public func findTheStrings() -> [String]
public func processRandomNumbers(_ numbers: [Float])

Цикл C++ for может перебирать Array, возвращаемый findTheStrings:

#include <StringsAndNumbers-Swift.h>
void printTheFoundStrings() {
  auto stringsArray = StringsAndNumbers::findTheStrings();
  for (const auto &swiftString: stringsArray) {
    std::cout << (std::string)swiftString << ", ";
  }
}

Пустой Array можно создать в C++, изменить и затем передать в Swift. Например, код C++ может создать Array, содержащий некоторые числа с плавающей запятой, и передать его в processRandomNumbers:

#include <StringsAndNumbers-Swift.h>
void processSomeTrulyUniqueRandomNumbers() {
  auto array = swift::Array<float>::init();
  array.append(1.0f);
  array.append(42.0f);
  StringsAndNumbers::processRandomNumbers(array);
}

Доступ к отдельным элементам массива в C++ осуществляется с помощью operator []. Однако, вы не можете изменить элемент массива, используя operator []. В C++ пока не поддерживается изменение отдельных элементов в Swift-массиве Array.

Представление Array в C++ предоставляет доступ к ряду методов и свойств массива, включая:

  • getCount
  • getCapacity
  • append
  • insertAt
  • removeAt

Доступны и другие методы и свойства, не указанные здесь.

Использование Swift-типов Optional в C++ Optional in C++ section" href="#using-swift-optional-in-c">

Тип Optional Swift доступен в C++. C++ представляет его с помощью шаблона класса swift::Optional. Он должен быть инстанцирован с типом C++, представляющим Swift-тип. Он также может быть инстанцирован с помощью родного типа C++, когда Swift-Array такого типа используется в Swift и экспортируется обратно в C++ через публичный Swift API.

Значение, хранящееся в Swift-типе Optional, можно извлечь, используя член-функцию get. Например, рассмотрим интерфейс модуля Swift OptionalValues:

// Swift module 'OptionalValues'

public func maybeMakeString() -> String?
public func callMeOnThePhoneMaybe(_ number: UInt64?)

Член-функция C++ get может использоваться для извлечения значения String, возвращённого maybeMakeString:

#include <OptionalValues-Swift.h>
void printAStringOrNone() {
  auto maybeString = OptionalValues::maybeMakeString();
  if (maybeString) {
    std::cout << (std::string)maybeString.get() << "\n";
  } else {
    std::cout << "Got no value from Swift :( \n";
  }
}

Тип Optional неявным образом преобразуется в bool в C++. Это позволяет проверить, содержит ли он значение в условии if, как показано в примере выше.

Swift-тип Optional также может быть создан из C++, используя конструктор some или none:

#include <OptionalValues-Swift.h>
void callMeOnThePhone() {
  OptionalValues::callMeOnThePhoneMaybe(
    swift::Optional<uint64_t>::some(1234567890));
}

Приложение

Этот раздел содержит дополнительные таблицы и ссылки по определённым темам, изложенным в вышеприведённой документации.

Список макросов кастомизации в <swift/bridging> <swift/bridging> section" href="#list-of-customization-macros-in-swiftbridging">

Макрос Документация
SWIFT_NAME Переименование C++ API в Swift
SWIFT_COMPUTED_PROPERTY Сопоставление геттеров и сеттеров с вычисляемыми свойствами
SWIFT_CONFORMS_TO_PROTOCOL Соответствие шаблона класса Swift-протоколу
SWIFT_IMMORTAL_REFERENCE Бессмертные типы ссылок
SWIFT_SHARED_REFERENCE Типы общих ссылок
SWIFT_UNSAFE_REFERENCE Небезопасные типы ссылок
SWIFT_RETURNS_INDEPENDENT_VALUE Аннотирование методов, возвращающих независимые ссылки или представления
SWIFT_MUTATING Функции-члены с модификатором const не должны изменять объект
SWIFT_NONCOPYABLE C++ структуры и классы по умолчанию — значения
SWIFT_SELF_CONTAINED Аннотирование C++ структур или классов как самодостаточных

История изменений документа

В этом разделе перечислены недавние изменения, внесённые в это справочное руководство.

2024-08-12

  • Добавлены несколько макросов кастомизации из <swift/bridging> в список.

2024-06-11

  • В Swift теперь доступны некопируемые C++ типы.

2024-03-26

  • Обновлён статус поддержки операторов C++ с шаблонами в Swift.

2023-06-05

  • Опубликована начальная версия руководства, описывающего, как комбинировать Swift и C++.

The Swift Programming Language, Copyright © 2014-2025 Apple Inc.
Swift and the Swift logo are trademarks of Apple Inc.

Documentation for Swift 6.0.3


https://www.swift.org/documentation/cxx-interop/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API