2012/09/21

【Objective-C++】ARCとstd::shared_ptr

季節は、秋である。


僕はRetina MacBook Proを買って以来、iPhone 5を予約したり天国のスティーブジョブズ様に祈りを捧げたりと大忙しで、ブログの更新をサボりがちになっていた。

さて。

そんなAppleの先進的なプロダクトの中核をなす開発言語と言えば、Objective-Cである。

嘗てはC言語の陰に隠れてマイナーなポジションに在った言語であるが、昨今のiPhoe / iPadの普及に伴い、その知名度もようやく上がりつつある。

というわけで、僕もObjective-Cに再挑戦してみようと思い、荻原剛志著『詳解Objective-C 2.0 第3版』なんぞを買ってきて読んで今に至るわけである。

ここから本題であるが、Objective-Cを用いてプログラミングを行う上でネックなポイントが『メモリ管理』であった。この話は去年の3月頃にも一度書いた気がする。

あれから時代は流れ、Objective-Cではメモリ管理を自動化するための ARC (Automatic Reference Counting) という機能が使用できるようになった。もうretainやらreleaseやら手作業でリファレンスカウンタをいじらなくてもよいのだ。

しかし、例えば僕がiPhoneアプリを開発する事になり、UIまわりをObjective-Cで、ロジックをC++でそれぞれコーディングする事にしたとしよう。この場合、UIからロジックにアクセスするために、Objective-C側は何らかの方法でC++側のオブジェクトを参照しておく必要がある。

残念ながらARC は C++ の生ポインタを自動解放してはくれないため、このままではObjective-C側にC++のメモリ解放コードが混在する穢らわしいコードになってしまう。安全性も下がる。たちまちメモリリークが発生し、またたく間にアプリが落ち、やがてApp Storeのレビューには心ないユーザからの罵詈雑言が書き込まれる事だろう。

だが悲観するには早い。

2011年8月12日(僕の誕生日!)に、C++の最新規格であるC++11がISOに承認され、参照カウンタを自動制御できるスマートポインタという仕組みが正式に導入された。

Visual Studioはどうか知らんが、最新のXcode 4.5ではC++11をサポートしている。これでObjective-C、C++ともにメモリ周りにおける懸念がずいぶん軽減された事になる。

しかしながら、ARCはObjective-Cだけの機能であり、スマートポインタはC++だけの機能である。

ここで、一つの疑問が生じた(ほこ×たてっぽく)。

【Objective-CのARCとC++のスマートポインタは共存できるのか】


というわけで、いろいろ実験してみたよー。今日の記事は備忘録的側面が強いので、何言ってるのか分からんという方は読み飛ばして下さい。

1. まずはARCを使ってみる

#import <Foundation/Foundation.h>
#import <cstdio>

/* 宣言部 */
@interface ObjectiveTercel : NSObject
-(id) init;
-(void) dealloc;
@end

/* 実装部 */
@implementation ObjectiveTercel

-(id) init {        // イニシャライザ
    self = [super init];
    printf("[こんにちは]\n");
    return self;
}

-(void) dealloc {   // ファイナライザ
    printf("[さようなら]\n");
}
@end

/* main関数 */
int main(int argc, const char * argv[])
{
    @autoreleasepool {
        ObjectiveTercel* tercel = [[ObjectiveTercel alloc] init];
    }
    return 0;
}


これは適当なクラスを作り、それをインスタンス化するコードである。

main()関数の内部では、インスタンスをポインタ参照している。動的にメモリをアロケートしているため、従来は明示的な解放コードが必要であった。

しかし、上記のコードを実行してみると、@autorelease ブロックを抜けるタイミングで dealloc が呼ばれ、メモリ解放が行われる。実行結果は以下の通りだ。

[こんにちは]
[さようなら]

もちろん、この手品にはタネがあり、ARCのために導入された構文上の制限を守らねばならないのだが。



2. C++11のスマートポインタを使ってみる

//#import <Foundation/Foundation.h>
#import <iostream>

/* 宣言部 */
class Tercel {
public:
    Tercel();
    virtual ~Tercel();
};

/* 実装部 */
Tercel::Tercel() {
    std::cout << "[Hello!]" << std::endl;
}
Tercel::~Tercel() {
    std::cout << "[Bye!]" << std::endl;
}

/* main関数 */
int main(int argc, const char * argv[])
{
    // @autoreleasepool {
        auto tercel = std::shared_ptr<Tercel>(new Tercel());
    // }
    return 0;
}

これも、明示的に解放コードを書いてはいない。代わりに、std::shared_ptrnewされたインスタンスに一枚皮をかぶせている。

実行すると、きちんとデストラクトされている事が判る。

[Hello!]
[Bye!]

余談だが、C++では明示的な初期化を伴う変数の宣言時にautoキーワードによって、長ったらしい型名の記述を省略できるようになった。右辺値を除いて auto tercel; と書くと、ビルド時に変数tercelの型を推論できず、構文エラーになるので注意されたい。



3. Objective-Cのインスタンス変数に、C++側オブジェクトのスマートポインタを持つ場合

最後に、両者を共存させるコードを示そう。サンプルは、Objective-Cで記述されたクラスが、C++側のオブジェクトの参照を持つ場合を想定している(※ ソースコードは展開してご覧ください)。

#import <Foundation/Foundation.h>
#import <iostream>
#import <cstdio>

/* C++のコード */
class Tercel {
public:
    Tercel() {
        std::cout << "[Hello!]" << std::endl;
    }
    
    virtual ~Tercel() {
        std::cout << "[Bye!]" << std::endl;
    }
};

/* Objective-Cのコード */
@interface ObjectiveTercel : NSObject
-(id) init;
-(void) dealloc;
@end

@implementation ObjectiveTercel {
    std::shared_ptr<Tercel> mHoge;
}
-(id) init {
    self = [super init];
    printf("[こんにちは]\n");
    
    mHoge = std::shared_ptr<Tercel>(new Tercel());
    return self;
}
-(void) dealloc {
    printf("[さようなら]\n");
}
@end


int main(int argc, const char * argv[])
{
    @autoreleasepool {
        ObjectiveTercel* tercel = [[ObjectiveTercel alloc] init];
    }
    return 0;
}

これを実行すると、

[こんにちは]
[Hello!]
[さようなら]
[Bye!]


と表示され、正常にメモリが解放されている。

というわけで、前置きでさんざんぐだぐだ書いて疲れたので一気にまとめるけど、ARCとスマートポインタは共存できたよ良かったね。

0 件のコメント:

コメントを投稿

ひとことどうぞφ(・ω・,,)