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を使ってみる

  1. #import <Foundation/Foundation.h>  
  2. #import <cstdio>  
  3.   
  4. /* 宣言部 */  
  5. @interface ObjectiveTercel : NSObject  
  6. -(id) init;  
  7. -(void) dealloc;  
  8. @end  
  9.   
  10. /* 実装部 */  
  11. @implementation ObjectiveTercel  
  12.   
  13. -(id) init {        // イニシャライザ  
  14.     self = [super init];  
  15.     printf("[こんにちは]\n");  
  16.     return self;  
  17. }  
  18.   
  19. -(void) dealloc {   // ファイナライザ  
  20.     printf("[さようなら]\n");  
  21. }  
  22. @end  
  23.   
  24. /* main関数 */  
  25. int main(int argc, const char * argv[])  
  26. {  
  27.     @autoreleasepool {  
  28.         ObjectiveTercel* tercel = [[ObjectiveTercel alloc] init];  
  29.     }  
  30.     return 0;  
  31. }  

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

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

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

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

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



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

  1. //#import <Foundation/Foundation.h>  
  2. #import <iostream>  
  3.   
  4. /* 宣言部 */  
  5. class Tercel {  
  6. public:  
  7.     Tercel();  
  8.     virtual ~Tercel();  
  9. };  
  10.   
  11. /* 実装部 */  
  12. Tercel::Tercel() {  
  13.     std::cout << "[Hello!]" << std::endl;  
  14. }  
  15. Tercel::~Tercel() {  
  16.     std::cout << "[Bye!]" << std::endl;  
  17. }  
  18.   
  19. /* main関数 */  
  20. int main(int argc, const char * argv[])  
  21. {  
  22.     // @autoreleasepool {  
  23.         auto tercel = std::shared_ptr<Tercel>(new Tercel());  
  24.     // }  
  25.     return 0;  
  26. }  

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

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

[Hello!]
[Bye!]

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



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

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


これを実行すると、

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


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

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

0 件のコメント:

コメントを投稿

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