Этот топик — мой перевод статьи про способы встраивания С++ в Objective-C проекты, в которой рассказаны некоторые интересные решения, как удачные, так и неудачные. Оригинал статьи:
www.philjordan.eu/article/strategies-for-using-c++-in-objective-c-projects
Итак,
Если вы спешите и хотите сразу перейти к решению проблемы внедрения объектов С++ в классы Objective-C без порчи заголовочных файлов, так, чтоб они могли быть включены из чистого Objective-C, вы можете прокрутить статью до заголовка Pimpl. Это решение можно использовать в ~95% случаев. Остальная часть содержит более глубокий анализ проблемы и дополнительные методы ее решения.
Используя Objective-C, обычно для программирования под iOs или под Mac, я часто сталкивался с ситуациями, когда нужно было вставить С++ в проект. Иногда самая хорошая библиотека для текущей задачи была написана на С++, иногда решение проблемы можно было более лаконично сделать на С++. Самый банальный пример — шаблоны С++, спасающие от написания повторяющегося стандартного кода. Менее очевидно то, что Objective-C иногда слишком объектно-ориентирован. Это звучит как ересь для «людей-для-которых-каждый-предмет-это-объект», но для нетривиальных структур данных я зачастую нахожу объектную ориентацию слишком громоздкой, а Сишные структуры слишком слабыми. Использование же С++ подходит в самый раз.
Objective-C также довольно агрессивен в плане управления памятью, когда нет, например, сборщика мусора. STL (и его новое расширение shared_ptr) позволяет забыть про эту проблему, или, по крайней мере, сосредоточить ее в конструкторах/деструкторах, а не захламлять код release'ами и retain'ами. Конечно, это дело вкуса, и зависит от ситуации; автоматическое управление памятью помогает в коде со нетривиальными структурами данных или в алгоритмически сложном коде.
Еще один повод для смешивания Objective-C и C++ — противоположная ситуация, когда необходимо вызывать функции Objective-C из проекта С++. Распространенный пример — портирование игры или движка под платформы Apple. В таких случаях также можно применять описанные ниже приемы.
Наконец, можно использовать С++ для улучшения производительности. Гибкость сообщений Objective-C накладывает некоторые издержки по сравнению с реализацией большей части виртуальных функций С++, даже учитывая техники кэширования, которые используются в современных рантаймах. Объекты Objective-C не имеют эквивалентных быстрых невиртуальных функций С++. Для оптимизации это может быть важным фактором.
Один из возможных способов использовать эти два языка программирования в одном проекте — полностью разделить их, позволив взаимодействовать через чистый С. Таким образом, можно будет предотвратить их «смешение». Выглядеть это будет так: код, использующий библиотеку С++ переносится в .cpp файл, интерфейс объявлен в заголовочном файле С, С++ часть реализует этот интерфейс с помощью extern «C» функций, а код, в котором будет происходить обращение к интерфейсу С — чистый Objective-C (.m).
Это будет работать неплохо в простых случаях, но более вероятно, что придется писать некоторую оболочку. Тот, у кого есть опыт динамически подгружаемых С++ библиотек с помощью открытого интерфейса С хорошо это знает.
Сегодня фактически весь Objective-C компилируется с помощью GCC или clang. Оба компилятора поддерживают Objective-C++, а это означает, что существует более удобный способ смешать языки.
На первый взгляд, использование диалекта Objective-C++ выглядит самым прямолинейным подходом. Это результат слияния С++ и Objective-C вместе в одном компиляторе, надежные реализации которых есть и в GCC, и в clang. Учитывая то, насколько различны Objective-C и C++, программисты GCC сделали трудоемкую работу. Но стоит только начать переименовывать .m файлы в .mm для их объявлени как С++ файлов, приходит понимание, что не все так просто.
Заголовочные файлы и препроцессор С на протяжении многих лет являются причиной головной боли программистов С, С++ и Objective-C, а при смешивании этих языков все становится еще хуже. Допустим, необходимо использовать контейнер map(словарь, ассоциативный массив) из библиотеки STL в классе Objective-C. Насколько я знаю, фреймворк Foundation от Apple не содержит отсортированного map'a, структурно построенного на деревьях. Итак, создаем переменную-член в нашем классе:
Однако, std::map<int, id> имеет смысл только для компилятора, поддерживающего С++, и только после #include , так что теперь этот заголовочный файл может быть импортирован (#import) только из файлов Objective-C++. А любой код, использующий этот класс теперь должен быть конвертирован в Objective-C++. Далее, по цепочке, остальные заголовочные файлы также придется конвертировать (.mm), и так со всем проектом.
В некоторых случаях это допустимо. Несмотря на это, менять весь проект или его большую часть только для того, чтобы в одном месте использовать библиотеку, — слишком громоздко и избыточно; кроме того, если вы единственный в проекте среди Objective-C программистов, знающий С++, это будет не очень хорошей идеей. Кроме того, проблемы могут возникнуть при компиляции кода на чистом С компилятором С++, редко случается, что такое проходит полностью безболезненно. Сверх того, это значит, что код нельзя будет повторно использовать автоматически �� других проектах Objective-C.
В большинстве же случаев, выход, позволяющий использовать преимущества кода на чистом Objective-C или C++ — это использование Objective-C++ там, и только там, где необходимо. Чтобы сделать это идеально, придется постараться.
Становится понятно, что цель — убрать все, что есть от С++, из заголовочных файлов. Типичный для С способ скрыть тип — указатель на void. Здесь, конечно, это тоже будет работать:
В тех местах кода, где будет использоваться таблица, придется использовать явное приведение типов: static_cast<std::map<int, id>*>(lookupTable) или ((std::map<int, id>*)lookupTable), что будет сильно раздражать. Если действительный тип члена класса изменится, все приведения типов придется менять вручную, а это резко увеличивает вероятность ошибиться. С ростом числа членов запомнить все правильные типы становится невозможно, и в итоге получаем недостатки как статического, так и динамического типизирования. Использование этого способа для работы с объектами из иерархии классов — это опасная игра с огнем, в которой может еще и оказаться, что указатели А* и В* на один и тот же объект имеют разные представления void*
Можно сделать и лучше.
Потеря информации о типе — это плохо, но раз уж типизированные поля С++ используются из кода Objective-C, а чистому Objective-C компилятору необходимо знать только об их присутствии (для корректного выделения памяти), почему бы не сделать две различные версии кода? В Objective-C++ определен символ препроцессора _cplusplus, поэтому как насчет такой реализации:
Некрасиво, но с этим гораздо легче работать. Стандарт С++ не гарантирует, что указатель на класс и указатель на void будут иметь одно и то же расположение в памяти (объект, конечно, один), но Objective-C++ не является стандартом GNU/Apple. На практике же проблему представляют только указатели на виртуальные функции, когда происходит конвертация в void*, а при попытке это сделать компилятор будет громко ругаться. Если же беспокойство не проходит, можно использовать static_cast<> вместо приведения в стиле С.
С по-прежнему удачно приводит void* к любому другому указателю неявным образом, поэтому, возможно, предпочтительно было бы заменить Сишную часть с #ifdef на указатель на структуру со сложным, но уникальным и узнаваемым названием, например, struct MyPrefix_std_map_int_id. Можно даже определить макро, расширяющееся в корректное определение типа в зависимости от компилятора:
При таком способе не получится избежать условного #include заголовков С++, а это может запутать людей, которые не знают или не любят С++. Да и выглядит это не очень. К счастью, есть другие решения.
Многие С++ программисты хорошо знакомы с чисто виртуальными функциями, и, следовательно, абстрактными классами. Другие языки, такие как Java и С#, имеют явную концепцию «интерфейса», при которой намеренно скрываются детали реализации. Этот же паттерн можно использовать и в нашем случае.
Последние версии Objective-C поддерживают протоколы, похожие на интерфейсы Java/C# по сути, если не по синтаксису. Можно объявить открытые методы класса в протоколе в заголовочном файле, а объявить и написать конкретный класс, реализующий указанный протокол, в закрытом коде (разделив таким образом, С++ и Objective-C). Это будет работать для методов экземпляра, но не получится напрямую создать экземпляр класса через протокол. Поэтому придется делегировать распределение памяти и инициализацию новых объектов фабрике классов или функции С. Хуже то, что протоколы в каком-то смысле ортогональны классам, поэтому при объявлении ссылки будут выглядеть по-другому:
вместо ожидаемого:
Все работает, но и это не идеал.
Так что там насчет абстрактных классов? В этом языке нет прямой идиоматической поддержки для них, но даже распространенный NSString является абстрактным, и невозможно понять это, просто используя его. Один из альтернативных способов решения нашей проблемы — взять все объявления методов и поместить их все в такой класс. Придется терпеть предупреждения компилятора о неполном описании класса. В рантайме попытки вызвать несуществующий метод будут бросать исключения. Можно, конечно, создать псевдо-реализации этих методов, которые будут бросать специальные исключения, объясняющие ситуацию.
В большинстве языков для создания экземпляров нужно знать конкретный класс, либо делегировать это фабрике классов. Занимательно, что можно напрямую послать классу NSString сообщения alloc и init, а получить экземпляры классов, унаследованных от NSString. Сообщения init возвращают объект, отличный от self, которому было послано init, например, alloc у NSString может быть переопределен так, что вызывается NSCFString, тут фабрикой классов является NSString. Если делать такое самому — придется определить все init* методы, используемые конкретным классом, для абстрактного, иначе они не будут видны тем, кто им пользуется.
Таким образом, объявить все методы в абстрактном классе, наслеюдуя от него Objective-C++ классы — определенно самое непыльное решение для заголовочного файла и для тех, кто пользуется этим классом, но, одновременно, оно является самым трудоемким, требующим дополнительного класса, псевдо-методов и нетривиальных реализаций init.
Однако же, программисты С++ нашли элегантное решение подобным проблемам. Одна из серьезных проблем больших проектов С++ — это резко возрастающее время компиляции, связанное с зависимостями заголовочных файлов. В таких случаях обычно делается сокрытие внутренностей класса от использующих его программистов. Точно такое же решение можно применить к дилемме Objective-C/C++.
Pimpl — сокращение от «указатель на реализацию» (pointer to implementation) или «закрытая реализация» (private implementation). Эта идиома довольно проста. В открытом заголовочном файле добавляется опережающее описание реализации struct, обычно, используя имя открытого класса с суффиксом Impl, зависит от соглашения. Эта struct будет содержать все члены, которые необходимо спрятать от открытого заголовочного файла класса. Осталось добавить указатель на структуру в переменные класса и определить члены структуры в .cpp файле (в нашем случае — .mm). В конструкторе (точнее, в -init*), с помощью оператора new нужно создать экземпляр структуры, присвоить ее переменной класса и убедиться, что в -dealloc вызывается delete.
Все будет работать, потому что опережающее объявление структур — это допустимый код на С, даже если потом окажется, что в структуре присутствуют явные или неявные конструкторы С++ или даже родительские классы. Открытые методы класса имеют доступ к стуруктуре через указатель, а создание и удаление происходит с помощью new/delete.
Зависит от реализации, насколько большая функциональность будет у методов открытого класса. Можно повысить производительность, если в некоторых случаях избежать пересылки сообщений Objective-C, но стоит помнить о неприятностях, которые могут возникнуть, когда методы С++ должны посылать сообщения классам Objective-C.
Стоит отметить, что вместо реализации в MyClass структуры, можно унаследовать от нее MyClass. Так можно избежать непрямых вызовов:
Но с увеличением количества членов это становится непрактичным из-за большого количества new/delete.
На чистом С++ имплементацию можно сделать классом, но в нашем случае это не пройдет, так как опережающее объявление должно быть корректным на Objective-C. В литературе по С++ можно встретить рекомендации использования shared_ptr<> и auto_ptr<> для автоматического удаления объектов. В заголовочных файлах Objective-C такое тоже не пройдет. Также не удасться уйти от указателя на струткуру, так как компилятор должен знать, сколько под нее необходимо выделить памяти.
Имплементация закрыта, поэтому невозможен прямой доступ к членам классов-наследников. Тем не менее, можно вынести объявления структуры в полу-закрытый заголовочный файл, который будет включаться только классами-наследниками, которые должны иметь этот прямой доступ. Такие наследники должны быть написаны на Objective-C++.
Все-таки я нахожу идиому Pimpl лучшим выбором для встраивания С++ в Objective-C почти во всех случаях. Даже если струк��ура содержит только один член, отсутствие лишних приведений типов возмещает косвенность. Производительность при этом не теряется, так как структура сама по себе не является указателем. В случае, когда необходимо включить Objective-C в С++, поступить можно таким же способом: определить открытый интерфейс С++, опережающее объявление для имплементации класса в заголовочном файле и поместить определение Objective-C членов в соответствующий .mm файл.
www.philjordan.eu/article/strategies-for-using-c++-in-objective-c-projects
Итак,
Способы встраивания C++ в Objective-C проекты
Если вы спешите и хотите сразу перейти к решению проблемы внедрения объектов С++ в классы Objective-C без порчи заголовочных файлов, так, чтоб они могли быть включены из чистого Objective-C, вы можете прокрутить статью до заголовка Pimpl. Это решение можно использовать в ~95% случаев. Остальная часть содержит более глубокий анализ проблемы и дополнительные методы ее решения.
Зачем смешивать Objective-C и C++?
Используя Objective-C, обычно для программирования под iOs или под Mac, я часто сталкивался с ситуациями, когда нужно было вставить С++ в проект. Иногда самая хорошая библиотека для текущей задачи была написана на С++, иногда решение проблемы можно было более лаконично сделать на С++. Самый банальный пример — шаблоны С++, спасающие от написания повторяющегося стандартного кода. Менее очевидно то, что Objective-C иногда слишком объектно-ориентирован. Это звучит как ересь для «людей-для-которых-каждый-предмет-это-объект», но для нетривиальных структур данных я зачастую нахожу объектную ориентацию слишком громоздкой, а Сишные структуры слишком слабыми. Использование же С++ подходит в самый раз.
Objective-C также довольно агрессивен в плане управления памятью, когда нет, например, сборщика мусора. STL (и его новое расширение shared_ptr) позволяет забыть про эту проблему, или, по крайней мере, сосредоточить ее в конструкторах/деструкторах, а не захламлять код release'ами и retain'ами. Конечно, это дело вкуса, и зависит от ситуации; автоматическое управление памятью помогает в коде со нетривиальными структурами данных или в алгоритмически сложном коде.
Еще один повод для смешивания Objective-C и C++ — противоположная ситуация, когда необходимо вызывать функции Objective-C из проекта С++. Распространенный пример — портирование игры или движка под платформы Apple. В таких случаях также можно применять описанные ниже приемы.
Наконец, можно использовать С++ для улучшения производительности. Гибкость сообщений Objective-C накладывает некоторые издержки по сравнению с реализацией большей части виртуальных функций С++, даже учитывая техники кэширования, которые используются в современных рантаймах. Объекты Objective-C не имеют эквивалентных быстрых невиртуальных функций С++. Для оптимизации это может быть важным фактором.
Приводя к общему знаменателю: С
Один из возможных способов использовать эти два языка программирования в одном проекте — полностью разделить их, позволив взаимодействовать через чистый С. Таким образом, можно будет предотвратить их «смешение». Выглядеть это будет так: код, использующий библиотеку С++ переносится в .cpp файл, интерфейс объявлен в заголовочном файле С, С++ часть реализует этот интерфейс с помощью extern «C» функций, а код, в котором будет происходить обращение к интерфейсу С — чистый Objective-C (.m).
Это будет работать неплохо в простых случаях, но более вероятно, что придется писать некоторую оболочку. Тот, у кого есть опыт динамически подгружаемых С++ библиотек с помощью открытого интерфейса С хорошо это знает.
Сегодня фактически весь Objective-C компилируется с помощью GCC или clang. Оба компилятора поддерживают Objective-C++, а это означает, что существует более удобный способ смешать языки.
Objective-C++ и проблемы с заголовочными файлами
На первый взгляд, использование диалекта Objective-C++ выглядит самым прямолинейным подходом. Это результат слияния С++ и Objective-C вместе в одном компиляторе, надежные реализации которых есть и в GCC, и в clang. Учитывая то, насколько различны Objective-C и C++, программисты GCC сделали трудоемкую работу. Но стоит только начать переименовывать .m файлы в .mm для их объявлени как С++ файлов, приходит понимание, что не все так просто.
Заголовочные файлы и препроцессор С на протяжении многих лет являются причиной головной боли программистов С, С++ и Objective-C, а при смешивании этих языков все становится еще хуже. Допустим, необходимо использовать контейнер map(словарь, ассоциативный массив) из библиотеки STL в классе Objective-C. Насколько я знаю, фреймворк Foundation от Apple не содержит отсортированного map'a, структурно построенного на деревьях. Итак, создаем переменную-член в нашем классе:
#include <map>
@interface MyClass : NSObject {
@private
std::map<int, id> lookupTable;
}
// ...
@end
Однако, std::map<int, id> имеет смысл только для компилятора, поддерживающего С++, и только после #include , так что теперь этот заголовочный файл может быть импортирован (#import) только из файлов Objective-C++. А любой код, использующий этот класс теперь должен быть конвертирован в Objective-C++. Далее, по цепочке, остальные заголовочные файлы также придется конвертировать (.mm), и так со всем проектом.
В некоторых случаях это допустимо. Несмотря на это, менять весь проект или его большую часть только для того, чтобы в одном месте использовать библиотеку, — слишком громоздко и избыточно; кроме того, если вы единственный в проекте среди Objective-C программистов, знающий С++, это будет не очень хорошей идеей. Кроме того, проблемы могут возникнуть при компиляции кода на чистом С компилятором С++, редко случается, что такое проходит полностью безболезненно. Сверх того, это значит, что код нельзя будет повторно использовать автоматически �� других проектах Objective-C.
В большинстве же случаев, выход, позволяющий использовать преимущества кода на чистом Objective-C или C++ — это использование Objective-C++ там, и только там, где необходимо. Чтобы сделать это идеально, придется постараться.
Стреляя в собственную ногу: void*
Становится понятно, что цель — убрать все, что есть от С++, из заголовочных файлов. Типичный для С способ скрыть тип — указатель на void. Здесь, конечно, это тоже будет работать:
@interface MyClass : NSObject {
@private
// на самом деле std::map<int, id>*
void* lookupTable;
}
// ...
@end
В тех местах кода, где будет использоваться таблица, придется использовать явное приведение типов: static_cast<std::map<int, id>*>(lookupTable) или ((std::map<int, id>*)lookupTable), что будет сильно раздражать. Если действительный тип члена класса изменится, все приведения типов придется менять вручную, а это резко увеличивает вероятность ошибиться. С ростом числа членов запомнить все правильные типы становится невозможно, и в итоге получаем недостатки как статического, так и динамического типизирования. Использование этого способа для работы с объектами из иерархии классов — это опасная игра с огнем, в которой может еще и оказаться, что указатели А* и В* на один и тот же объект имеют разные представления void*
Можно сделать и лучше.
Условная компиляция
Потеря информации о типе — это плохо, но раз уж типизированные поля С++ используются из кода Objective-C, а чистому Objective-C компилятору необходимо знать только об их присутствии (для корректного выделения памяти), почему бы не сделать две различные версии кода? В Objective-C++ определен символ препроцессора _cplusplus, поэтому как насчет такой реализации:
#ifdef __cplusplus
#include <map>
#endif
@interface MyClass : NSObject {
@private
#ifdef __cplusplus
std::map<int, id>* lookupTable;
#else
void* lookupTable;
#endif
}
// ...
@end
Некрасиво, но с этим гораздо легче работать. Стандарт С++ не гарантирует, что указатель на класс и указатель на void будут иметь одно и то же расположение в памяти (объект, конечно, один), но Objective-C++ не является стандартом GNU/Apple. На практике же проблему представляют только указатели на виртуальные функции, когда происходит конвертация в void*, а при попытке это сделать компилятор будет громко ругаться. Если же беспокойство не проходит, можно использовать static_cast<> вместо приведения в стиле С.
С по-прежнему удачно приводит void* к любому другому указателю неявным образом, поэтому, возможно, предпочтительно было бы заменить Сишную часть с #ifdef на указатель на структуру со сложным, но уникальным и узнаваемым названием, например, struct MyPrefix_std_map_int_id. Можно даже определить макро, расширяющееся в корректное определение типа в зависимости от компилятора:
#ifdef __cplusplus
#define OPAQUE_CPP_TYPE(cpptype, ctype) cpptype
#else
#define OPAQUE_CPP_TYPE(cpptype, ctype) struct ctype
#endif
// ...
#ifdef __cplusplus
#include <map>
#endif
@interface MyClass : NSObject {
@private
OPAQUE_CPP_TYPE(std::map<int, id>, cpp_std_map_int_id)* lookupTable;
}
// ...
@end
При таком способе не получится избежать условного #include заголовков С++, а это может запутать людей, которые не знают или не любят С++. Да и выглядит это не очень. К счастью, есть другие решения.
Абстрактные классы, интерфейсы и протоколы
Многие С++ программисты хорошо знакомы с чисто виртуальными функциями, и, следовательно, абстрактными классами. Другие языки, такие как Java и С#, имеют явную концепцию «интерфейса», при которой намеренно скрываются детали реализации. Этот же паттерн можно использовать и в нашем случае.
Последние версии Objective-C поддерживают протоколы, похожие на интерфейсы Java/C# по сути, если не по синтаксису. Можно объявить открытые методы класса в протоколе в заголовочном файле, а объявить и написать конкретный класс, реализующий указанный протокол, в закрытом коде (разделив таким образом, С++ и Objective-C). Это будет работать для методов экземпляра, но не получится напрямую создать экземпляр класса через протокол. Поэтому придется делегировать распределение памяти и инициализацию новых объектов фабрике классов или функции С. Хуже то, что протоколы в каком-то смысле ортогональны классам, поэтому при объявлении ссылки будут выглядеть по-другому:
id<MyProtocol> ref;
вместо ожидаемого:
MyClass* ref;
Все работает, но и это не идеал.
Абстрактные классы в Objective-C
Так что там насчет абстрактных классов? В этом языке нет прямой идиоматической поддержки для них, но даже распространенный NSString является абстрактным, и невозможно понять это, просто используя его. Один из альтернативных способов решения нашей проблемы — взять все объявления методов и поместить их все в такой класс. Придется терпеть предупреждения компилятора о неполном описании класса. В рантайме попытки вызвать несуществующий метод будут бросать исключения. Можно, конечно, создать псевдо-реализации этих методов, которые будут бросать специальные исключения, объясняющие ситуацию.
В большинстве языков для создания экземпляров нужно знать конкретный класс, либо делегировать это фабрике классов. Занимательно, что можно напрямую послать классу NSString сообщения alloc и init, а получить экземпляры классов, унаследованных от NSString. Сообщения init возвращают объект, отличный от self, которому было послано init, например, alloc у NSString может быть переопределен так, что вызывается NSCFString, тут фабрикой классов является NSString. Если делать такое самому — придется определить все init* методы, используемые конкретным классом, для абстрактного, иначе они не будут видны тем, кто им пользуется.
Таким образом, объявить все методы в абстрактном классе, наслеюдуя от него Objective-C++ классы — определенно самое непыльное решение для заголовочного файла и для тех, кто пользуется этим классом, но, одновременно, оно является самым трудоемким, требующим дополнительного класса, псевдо-методов и нетривиальных реализаций init.
Однако же, программисты С++ нашли элегантное решение подобным проблемам. Одна из серьезных проблем больших проектов С++ — это резко возрастающее время компиляции, связанное с зависимостями заголовочных файлов. В таких случаях обычно делается сокрытие внутренностей класса от использующих его программистов. Точно такое же решение можно применить к дилемме Objective-C/C++.
Pimpl
Pimpl — сокращение от «указатель на реализацию» (pointer to implementation) или «закрытая реализация» (private implementation). Эта идиома довольно проста. В открытом заголовочном файле добавляется опережающее описание реализации struct, обычно, используя имя открытого класса с суффиксом Impl, зависит от соглашения. Эта struct будет содержать все члены, которые необходимо спрятать от открытого заголовочного файла класса. Осталось добавить указатель на структуру в переменные класса и определить члены структуры в .cpp файле (в нашем случае — .mm). В конструкторе (точнее, в -init*), с помощью оператора new нужно создать экземпляр структуры, присвоить ее переменной класса и убедиться, что в -dealloc вызывается delete.
MyClass.h:
// ...
struct MyClassImpl;
@interface MyClass : NSObject {
@private
struct MyClassImpl* impl;
}
// объявления открытых методов...
- (id)lookup:(int)num;
// ...
@end
MyClass.mm:
#import "MyClass.h"
#include <map>
struct MyClassImpl {
std::map<int, id> lookupTable;
};
@implementation MyClass
- (id)init
{
self = [super init];
if (self)
{
impl = new MyClassImpl;
}
return self;
}
- (void)dealloc
{
delete impl;
[super dealloc];
}
- (id)lookup:(int)num
{
std::map<int, id>::const_iterator found =
impl->lookupTable.find(num);
if (found == impl->lookupTable.end()) return nil;
return found->second;
}
// ...
@end
Все будет работать, потому что опережающее объявление структур — это допустимый код на С, даже если потом окажется, что в структуре присутствуют явные или неявные конструкторы С++ или даже родительские классы. Открытые методы класса имеют доступ к стуруктуре через указатель, а создание и удаление происходит с помощью new/delete.
Зависит от реализации, насколько большая функциональность будет у методов открытого класса. Можно повысить производительность, если в некоторых случаях избежать пересылки сообщений Objective-C, но стоит помнить о неприятностях, которые могут возникнуть, когда методы С++ должны посылать сообщения классам Objective-C.
Стоит отметить, что вместо реализации в MyClass структуры, можно унаследовать от нее MyClass. Так можно избежать непрямых вызовов:
@interface MyClass : NSObject {
struct MyClassMap* lookupTable;
}
- (id)lookup:(int)i;
@end
and MyClass.mm:
#import "MyClass.h"
#include <map>
struct MyClassMap : std::map<int, id> { };
@implementation MyClass
- (id)init {
self = [super init];
if (self)
lookupTable = new MyClassMap;
return self;
}
- (id)lookup:(int)i {
MyClassMap::const_iterator found = lookupTable->find(i);
return (found == lookupTable->end()) ? nil : found->second;
}
- (void)dealloc {
delete lookupTable; lookupTable = NULL;
[super dealloc];
}
@end
Но с увеличением количества членов это становится непрактичным из-за большого количества new/delete.
Ограничения
На чистом С++ имплементацию можно сделать классом, но в нашем случае это не пройдет, так как опережающее объявление должно быть корректным на Objective-C. В литературе по С++ можно встретить рекомендации использования shared_ptr<> и auto_ptr<> для автоматического удаления объектов. В заголовочных файлах Objective-C такое тоже не пройдет. Также не удасться уйти от указателя на струткуру, так как компилятор должен знать, сколько под нее необходимо выделить памяти.
Имплементация закрыта, поэтому невозможен прямой доступ к членам классов-наследников. Тем не менее, можно вынести объявления структуры в полу-закрытый заголовочный файл, который будет включаться только классами-наследниками, которые должны иметь этот прямой доступ. Такие наследники должны быть написаны на Objective-C++.
Мысли в конце
Все-таки я нахожу идиому Pimpl лучшим выбором для встраивания С++ в Objective-C почти во всех случаях. Даже если струк��ура содержит только один член, отсутствие лишних приведений типов возмещает косвенность. Производительность при этом не теряется, так как структура сама по себе не является указателем. В случае, когда необходимо включить Objective-C в С++, поступить можно таким же способом: определить открытый интерфейс С++, опережающее объявление для имплементации класса в заголовочном файле и поместить определение Objective-C членов в соответствующий .mm файл.