Spec-Zone.ru › Swift

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

Содержание

  • Введение
  • Обзор
    • Включение межъязыковой совместимости C++
    • Импорт C++ в Swift
    • Создание модуля Clang
    • Работа с импортированными API C++
    • Экспонирование API Swift для C++
    • Гарантии стабильности исходного кода для кодовых баз смешанного языка
  • Использование типов и функций C++ в Swift
    • Вызов функций C++
    • Структуры и классы C++ по умолчанию являются типами значений
    • Создание типов C++ из Swift
    • Доступ к членам данных типа C++
    • Вызов функций-членов C++
      • Константные функции-члены являются неизменяемыми
      • Константные функции-члены не должны изменять объект
      • Функции-члены, возвращающие ссылки, по умолчанию небезопасны
      • Перегруженные функции-члены
      • Виртуальные функции-члены
      • Статические функции-члены
    • Доступ к унаследованным членам из 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++ как самодостаточных
END_OF_DOCUMENT_MARKER
  • Доступ к API Swift из C++
  • Использование типов и функций Swift из C++
    • Вызов функций Swift
    • Использование структур Swift в C++
      • Создание структуры Swift в C++
    • Использование классов Swift в C++
    • Использование перечислений Swift в C++
      • Использование перечислений со связанными значениями
    • Вызов методов Swift
    • Доступ к свойствам Swift из C++
  • Использование типов стандартной библиотеки Swift из C++
    • Использование String Swift в C++
    • Использование Array Swift в C++
    • Использование Optional Swift в C++
  • Приложение
    • Список макросов настройки в <swift/bridging>
  • История изменений документа

Введение

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

Этот документ — справочник по смешанному использованию Swift и C++. Он описывает, как C++ API импортируются в Swift, и приводит примеры, показывающие, как различные C++ API можно использовать в 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. После этого вы захотите просмотреть последующие разделы, которые описывают как использовать импортированные C++ API в Swift. Вы также должны ознакомиться с тем, как API Swift могут быть экспонированы для остальной части кодовой базы C++. Если вас интересует использование API Swift из C++, вы обязательно должны просмотреть последующие разделы, которые описывают как использовать экспонированные API Swift в C++.

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

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

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

  • Узнайте, как использовать C++ API из 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.

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

Компилятор 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 {
  ...
}

В заголовке, сгенерированном компилятором Swift для модуля forestRenderer, будет присутствовать встроенная функция C++, которая напрямую вызывает реализацию renderTreeToAscii. Ее можно вызвать из кода 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++, поэтому при попытке использовать то, что не экспонировано для 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 члена, которые различаются только константностью и типом возвращаемого значения:

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();
};

Структура Swift Fern получает дополнительный метод с именем 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++ со scope становятся перечислениями 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++ без scope становятся структурами Swift. Например, следующее перечисление C++ без scope enum становится структурой Swift:

enum MushroomKind {
  Oyster,
  Portobello,
  Button
}

Варианты перечисления C++ без scope становятся переменными вне структуры 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-средой разработки.

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

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

Переименование API C++ в 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

Для того, чтобы тип контейнера 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++

  • Используйте цикл 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!

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

END_OF_DOCUMENT_MARKER

Компилятор 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++ может вызывать Swift-API, которые принимают или возвращают типы общих ссылок C++. Объекты этих типов всегда создаются на стороне C++, но их ссылки могут передаваться между Swift и C++. Этот раздел объясняет правила инкремента и декремента счётчиков ссылок при передаче таких ссылок через языковую границу. Рассмотрим следующие Swift-API:

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++ стандартной библиотеки

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

END_OF_DOCUMENT_MARKER

Как указано ранее, методы-члены, возвращающие ссылки, указатели или определенные структуры/классы, содержащие ссылки или указатели, считаются небезопасными в 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.

Доступ к API Swift из 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, является копируемым и перемещаемым. Его конструкторы копирования и перемещения, а также его деструктор подчиняются правилам модели автоматического управления памятью Swift (ARC), которая позволяет программе освободить экземпляр класса 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 в C++" 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

Доступно несколько других методов и свойств.

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

Использование Swift Array в C++ Array в C++" 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 << ", ";
  }
}

В C++ можно создать пустой Array, изменить его и затем передать его в 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 в C++" href="#using-swift-optional-in-c">

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

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

// Swift module 'OptionalValues'

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

Функция-член get C++ может использоваться для извлечения значения 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>" 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