星一のはてなブログ

星一です。

Objective-C++11

この記事は Mac Dev JP Advent Calendar の参加記事 (6 日目) です。

はじめに

この記事では、 Objective-C++ で C++11 を使用する例について紹介します。

Objective-C++ とは

Objective-C++ は、プログラミング言語 Objective-C と C++ とを多重継承したような言語です。お互いの文法がかち合わないので、混在することができます。極稀に文法がコンフリクトしますが、個人的にはそれで困ったことはありません。文法的にほぼ直交し、お互いのオブジェクトを単なるポインタ値とみなしているため、混在させることができるのです。 Objective-C と C++ の機能をオーバーラップさせるようなことはできません。例えば、 Objective-C のクラスを C++ で継承したりするようなことはできません。 Objective-C の ARC を C++ のオブジェクトに適用するといったこともできません。ソースの拡張子は mm が一般的に使われます。

Objective-C++ を利用することで、 C++ の資産を使いつつ Cocoa アプリケーションを開発する、といったことが可能になります。あとは「C++ のヘッダファイルオンリーなライブラリを作りたいのにどうしても Cocoa 使わなきゃいけなくて、でも Cocoa は Objective-C から触らないといけなくて死ぬ」という場合も、 Objective-C++ は有用です。

問題点は、コンパイル時間が遅くなることです。ただでさえ複雑な文法を持つ Objective-C と C++ が混ざることで、コンパイル速度はかなり遅くなります。

Objective-C++ の例

#import <Cocoa/Cocoa.h>

@interface Bar : NSObject
@end

@implementation Bar
- (void)hello {
  NSLog(@"Hello!");
}   
@end

class Foo {
private:
  Bar* bar_;
public:
  Foo(Bar* bar)  
    : bar_(bar) {
  }
  void hello() {
    [this->bar_ hello];
  }
};

int main() {
  Bar* bar = [[Bar alloc] init];
  Foo foo(bar);
  foo.hello();
}

Objective-C の世界と C++ の世界とが見事に入り交じっていることがわかりますね。これを test.mm という名前で保存したとして、次のようにすればコンパイルされ、 test という名前の実行ファイルができます:

clang++ -x objective-c++ -stdlib=libc++ -framework Cocoa -o test test.mm

C++11 とは

C++11 は、 2011 年に ISO で承認された C++ の最新標準規格です。 ISO に承認される前までは C++0x と呼ばれていました。 C++03 に対し大幅な機能追加が行われています。

宣伝

Ebiten (海老天) という 2D ゲームライブラリを作っています。 C++ ヘッダファイルオンリーなライブラリを目指すため、本稿のテーマである Objective-C++11 を活用しています。 API やコンセプトが毎日のように変わるので実用段階には程遠いです。

準備

最新の clang++ が必要になりますが、これは最新の Xcode 4.2 に含まれています。 App Store で落としましょう。でかいので落とすのに毎度とても時間がかかります。ほんとうに何とかして欲しいですね…。ちなみに gcc では、 Objective-C++ は扱えますが、 Objective-C++ と C++11 とを両立させることができません。

clang++ オプション

clang++ に、以下のオプションが必要です:

-x objective-c++ # 言語の指定
-std=c++0x # C++11 での指定。近い将来には -std=c++11 になるはず。というかなっている?
-stdlib=libc++ # 標準ライブラリの指定

標準ライブラリの指定はなんで要るのかよく分からないのですが、指定しないと動きません。

Xcode での使い方

割愛。コンパイラオプション追加すればいけるでしょう。

実際にやってみた

型推論

例えば

NSApplication* app = [NSApplication sharedApplication];
HogehogeController* controller = [[HogehogeController alloc] init];
[app setDelegate:controller];
[app run];

と書いていたのを

auto app = [NSApplication sharedApplication];
auto controller = [[HogehogeController alloc] init];
[app setDelegate:controller];
[app run];

と書けるようになります。長い型名も書かなくて済みます。 id 型と違って、静的に型が定まるものなので、コンパイル時の検査がより強力になります。 C++11 の機能の中で、 Objective-C にも適用でき、かつ実用的である唯一の例だと思います。

範囲 for

// clang++ 3.0 では initializer list が未実装なので、仕方なく要素一個一個追加している
std::vector<std::string> cppArray;
cppArray.emplace_back("Foo");
cppArray.emplace_back("Bar");
cppArray.emplace_back("Baz");
// C++11 の範囲 for
for (auto str : cppArray) {
  cppResult += str;
}
NSLog(@"%@", [[NSString alloc] initWithCString:cppResult.c_str()
                                      encoding:NSUTF8StringEncoding]);

NSString* objcResult = @"";
NSArray* objcArray = [NSArray arrayWithObjects:@"Foo", @"Bar", @"Baz", nil];
// Objective-C の範囲 for
for (NSString* str in objcArray) {
  objcResult = [objcResult stringByAppendingString:str];
}
NSLog(@"%@", objcResult);

二種類の for が混在できます。細かい文法が違います (コロンと in)。とても不気味ですね。

NSArray に C++11 の範囲 for を適用させることは不可能ではないと思いますが、ほとんど意味が無いと思います。やるとしたら std::begin/end の NSArray* による特殊化を行うことになるでしょう。

その他

追加された標準ライブラリ (thread など) も問題なく使用出来ました。

nil の代わりに nullptr を書いたりも出来ました。ただメリットは皆無であり、お互い同じ値である保証は全く無いので、辞めたほうが良いでしょう。

clang++ には C++11 のフル機能が実装されているわけではありません。詳しくは Clang - C++ and C++'11 Status を参照しましょう。例えば 2011 年 12 月 6 日 (日本時間) 現在では、 Initializer List やラムダ式などが使えません。

まとめ

本記事では、 Objective-C++ で C++11 の機能を問題なく使えるよ、ということを示しました。「C++11 の新機能で Objective-C で使うとかっこいい機能」というのが思ったよりもなかったのが残念です。 Objective-C型推論出来てすげーと思って書き始めたものの、他に思ったより面白い例がなく、何が嬉しいのかよく分からない記事になってしまいました。

追伸

はてなブログってスーパー pre 記法に色つけられないのかな?