C++のクラスや資産をObjective-Cで活用する【ラップ方法とライフサイクルについて】

Objective-C上でC++のコードを扱うことはできませんが、Objective-CからObjective-C++経由でC++を扱うことはできます。C++をObjective-C++でラップし、C++製の資産を純Objective-Cソース上で間接的に利用することができるわけです。

やり方としては、C++クラスのインスタンスをObjective-C++クラスのインスタンス変数として保持する方法などが考えられます。なお本テクニックはSwift言語からC++のコードを扱うための作法としても有効です。

目次

C++のクラスをObjective-Cのクラスでラップする方法

C++のクラスを、Objective-C++クラスのインスタンス変数として宣言するだけです。インスタンス変数はヘッダファイル側ではなく、実装ファイル側の@implementation内で宣言します。

/* ObjCpp.mm */
#import "ObjCpp.h"
#import <iostream> // std::cout

/* C++ */
struct Cpp {
  void print() { std::cout << "Hello C++"; }
};

/* Objective-C++ */
@implementation ObjCpp {
  Cpp _cpp; // C++クラス
}

-(void)print {
  _cpp.print(); // "Hello C++"
}
@end
/* ObjCpp.h */
#import <Foundation/Foundation.h>

@interface ObjCpp : NSObject
-(void)print;
@end

Objective-C++(ObjCpp.mm)側ではC++固有の機能や外部ライブラリの利用が可能となります。ヘッダ側ではC++関連の宣言や定義を行わないようにすることもポイントです。そうするとObjCpp.hは通常のObjective-Cヘッダーとして扱われるようになるため、Objective-C++以外の一般的なObjective-CソースからもObjCppクラスが利用できるようになります。

/* main.m */
#import "ObjCpp.h"

int main() {
  ObjCpp *obj = [ObjCpp new];
  [obj print]; // "Hello C++"
}

汎用ポインタを用いた以前の方法について

今回紹介した方法は、一昔前の方法よりも随分とわかりやすくなっています。インスタンス変数の宣言が実装ファイル側の@implementation内で行えるようになったことが関係しています。

以前は、インスタンス変数の宣言はヘッダー側(@interface)でしか行えなかったため、C++クラス隠蔽用のダミーポインタ(void *)を宣言する必要がありましたが、最新のObjective-C環境ではそのイディオムは不要となります。

インスタンス変数のコンストラクタ呼び出しについて

メンバー変数(Cpp _cpp;)の初期化はObjective-C++側で自動的に行われます。またその際にはC++クラス側のデフォルトコンストラクタ呼び出しが行われる事になります。

デフォルトコンストラクタ呼び出しが制限されているクラスを利用した場合には、コンパイルエラーが発生します。

// ERROR: Call to deleted constructor of 'struct Cpp'
@implementation ObjCpp{
  struct Cpp { Cpp() = delete; } _cpp; }
@end

インスタンス変数のデストラクタ呼び出しについて

Objective-C++側のインスタンス(ObjCpp *obj)が解放されるタイミングで、自動的にインスタンス変数Cpp _cpp;に対するデストラクタ呼び出しが行われます。

ライフサイクルについて

デストラクタ呼び出しは-[ObjCpp .cxx_destruct]という隠しメソッド内で行われます。この隠しメソッドはデアロケータ(-[ObjCpp dealloc])実行後に呼び出されます。

またコンストラクタ呼び出し用の隠しメソッドは-[ObjCpp .cxx_construct]となっており、こちらはObjective-Cクラス側のallocメソッド実行時に呼び出されます。つまりC++のコンストラクタ呼び出しは、ObjCのイニシャライザ(init, initWithXXX:)よりも先に行われるという事になります。

コンストラクタを明示的に呼び出したい場合

C++オブジェクト・インスタンス変数の初期化はObjective-C側で自動的に行われるため、明示的なコンストラクタ呼び出しは行なえません。対応方法としては、デフォルトコンストラクタ呼び出しの後に、ObjC側のイニシャライザで個別に初期化をし直す方法や、ポインタ/スマートポインタのインスタンス変数をイニシャライザ側で手動で初期化する方法等が考えられます。ポインタ/スマートポインタを用いた初期化については、以下のページが参考になります。

C++のオブジェクトをポインタとして保持/管理する方法【Objective-C++】

広告