Entries

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Objective-C++ - オブジェクトの相互所有

タグ: C++ Objective-C

Objective-CのクラスにC++のクラスのインスタンスを持たせるには、ポインタを使う。
ポインタを使わず、直にインスタンス変数にすることができないわけではないが、コンストラクタもデストラクタも呼ばれず、vtblの初期化も行われないので、POD以外には使えない。

class Bar;
class Baz;

@interface Foo : NSObject
{
	NSString* string; ///< Objective-Cのクラスのインスタンス
	boost::shared_ptr<Bar>* bar; ///< C++のスマートポインタインスタンス
	Baz* baz; ///< C++のオブジェクトを指すポインタ
};
- (id)initWithString:(NSString*)str bar:(const boost::shared_ptr<Bar>&)br baz:(Baz*)bz;
@end

@implementation Foo
- (id)initWithString:(NSString*)str bar:(const boost::shared_ptr<Bar>&)br baz:(Baz*)bz
{
	if(self = [super init])
	{
		string = [str retain];
		bar = new boost::shared_ptr<Bar>(br);
		baz = bz;
	}
	return self;
}

- (void)dealloc
{
	delete bar;
	[string release];
	[super dealloc];
}
@end

C++のクラスにObjective-Cのクラスのインスタンスを持たせる場合は、生のポインタの他に、スマートポインタも使える。

class Bar;
class Baz;

class Foo
{
public:
	Foo(NSString* str, const boost::shared_ptr<Bar>& br, Baz* bz);
	
private:
	boost::intrusive_ptr<NSString> string; ///< Objective-Cのオブジェクトを指すスマートポインタ
	boost::shared_ptr<Bar> bar; ///< C++のオブジェクトを指すスマートポインタ
	Baz* baz; ///< C++のオブジェクトを指すポインタ
};

void intrusive_ptr_add_ref(id obj)
{
	[obj retain];
}

void intrusive_ptr_release(id obj)
{
	[obj release];
}

Foo::Foo(NSString* str, const boost::shared_ptr<Bar>& br, Baz* bz) :
string(str),
bar(br),
baz(bz)
{
}

06月24日のココロ日記(BlogPet)

みじん切りにした管理と手間を混ぜたらタイミングの味がしました。一つ勉強になりました。

*このエントリは、ブログペットのココロが書いてます♪

C++から見たObjective-C - オブジェクトの所有権

タグ: C++ Objective-C

Cocoaのオブジェクトは「参照カウンタ」によって所有権管理を行う。
(Objective-C 2.0にはガベージコレクタもあるが、使ったことがないので今回は無視)

id obj = [[NSString alloc] initWithString:@"Hoge"]; // alloc により参照カウンタ=1
[obj retain]; // retain により参照カウンタ=2
[obj release]; // release により参照カウンタ=1
[obj release]; // 参照カウンタ=0 となり dealloc が呼ばれ、オブジェクトは破壊される

このような単純な例では、alloc retain releaseは
alloc回数(必ず1) + retain回数 = release回数
となるので分かりやすい。

しかし実際に使う場合、これだけでは何かと面倒が多い。
例えばこのようなメソッドがあった場合、

- (NSString*)string

これは必ず「呼び出し側が戻り値をreleaseしなければならないメソッド」として実装しなければならない。
例え、このメソッドを持つオブジェクトが戻り値に対する所有権を持っていたとしても、オブジェクトをretainしてから返す。
そうしておかないと、「戻り値をreleaseしなければならないメソッド」と「戻り値をreleaseしてはならないメソッド」が混在して、ややこしいことになる。

で、releaseしなければならないということは、

NSLog("%@", [hoge string]);

こう書くことはできず、「一時保存」「処理」「解放」のため、処理を3行に分けて記述しなければならないということだ。

NSString* string = [hoge string]; // 一時保存
NSLog(@"%@", string); // 処理
[string release]; // 解放

これが地味に面倒だったりする。

このような手間を省くため、Cocoaのクラスにはもう一つ、所有権管理用のメソッドとしてautoreleaseが用意されている。

id obj = [NSString stringWithString:@"Hoge"]; // クラスメソッドにより参照カウンタ=1 + autorelease
[obj retain]; // retain により参照カウンタ=2 + autorelease
[obj release]; // release により参照カウンタ=1 + autorelease
// 以降、objが格納されているautorelease poolが破壊されたタイミングでreleaseが呼ばれる

autoreleaseの存在により、Cocoaには「allocで始まる名前のメソッド以外が返す戻り値はreleaseしてはならない(≒autoreleaseされている)」というルールが成立している。
よって、先ほどの

- (NSString*)string

の戻り値は、Cocoaでは「戻り値をreleaseしてはいけないメソッド」として実装する。

C++の場合、現標準ではろくな所有権管理機能がない(std::auto_ptrはそこそこ便利だが)ので、ここは素直にBoostTR1を使う。

boost::shared_ptr<std::string> str1(new std::string("Hoge")); // newされたオブジェクトを参照数=1で管理開始
{
	boost::shared_ptr<std::string> str2(str); // str2のコンストラクタにより参照数が2に
} // str2のデストラクタにより参照数が1に
// 以降、str1が破壊されるタイミングでデストラクタが働き参照数0となり、deleteが呼ばれる

特に理由がなければ、C++では生のポインタは所有権を持たない「弱い参照」としてのみ扱い、所有権を持つ「強い参照」はスマートポインタで扱った方が良いだろう。
「解放処理を呼ばなければならない生ポインタ」の存在は、autoreleaseがない時のstringメソッドの例同様、「解放処理を呼んではならない生ポインタ」との共存によりややこしいことになる。

C++から見たObjective-C - プロトコル

タグ: C++ Objective-C

既に前の記事で使っているが、これはObjective-Cにおける「インタフェース(Java用語?)」である。

@protocol Foo
- (void)append:(int)n;
@end

@interface Bar : NSObject <Foo>
{

C++では「純粋仮想関数しか持たないクラス」がこれに当たり、意識して「このようなクラス以外を多重継承しない」ことで、プロトコルとほぼ同等の働きをする。

class Foo
{
public:
	virtual ~Foo() {}
	virtual void append(int n) = 0;
};

class Bar : public Foo
{

同等と言っても、意図的にクラスの機能を限定して使っているだけなので、ルールを破る(プロトコルとして作っていたクラスにメンバ変数を追加したり、プロトコルクラス以外を多重継承したりする)のは容易。

C++から見たObjective-C - 高速列挙

タグ: C++ Objective-C
NSArray *array = [NSArray arrayWithObjects:
        @"One", @"Two", @"Three", @"Four", nil];

for (NSString *element in array) {
    NSLog(@"element:%@", element);
}

例は公式から引用
「for」の評価式部分に「in」を加えるという、かなり思い切った構文。
AppleScriptが紛れ込んだかと思った。
でもまあ、解りやすいし、便利。
名前に「高速」と付いているくらいだし、普通の列挙よりも速いのだろう。

C++でこれと同じことをするのはBoost.Foreach

using namespace boost::assign; // Boost.Assignmentを使う

std::vector<std::string> strings;
strings += "One", "Two", "Three", "Four";

BOOST_FOREACH(const std::string& str, strings)
{
	std::cout << "element:" << str << std::endl;
}

あとは、これらとは少し趣が異なるが似たものとして、標準C++には std::for_each() や std::copy() などの「アルゴリズム」が用意されている。

using namespace boost::assign; /

std::vector<std::string> strings;
strings += "One", "Two", "Three", "Four";

std::copy(
	strings.begin(),
	strings.end(),
	std::ostream_iterator<std::string>(std::cout, "\n")
);

MGS4

PS3と一緒に注文したら宅配ボックスに入らなくなったので、本日受け取り。
うむー、縦梱包なら入ったのにー。

とりあえず1章の途中までプレイ。
うーん、思ったより綺麗な気がしない。
フレームレートが低いのは映画っぽく見せる表現技法かとも思ったが、負荷が軽い所では普通にヌルヌル動くので、単なる描画演算の都合らしい。

あと、操作がかなり困難なのだが、これはまあ慣れるしかないか。
今のところは「攻撃は、えーと、R1(ぶんっ)ってこれは近接攻撃だ。Shiftキー、じゃない、L1で銃を構えるんだっけ。んー?どこを狙ってるんだ? 右スティックをこっち倒して……いや逆だ、逆。って痛い痛い、被弾してる。つーか自動照準使おう」とまあ酷いもの。
この調子じゃあ慣れるころにはゲーム終わってるのでは。
もう最近の「多ボタン自由視点ゲーム」にはついて行けんかもなぁ。

AssertMacros.h

タグ: Objective-C C++
#define check(assertion)

なにこのマクロ(;´Д`)
こんなのを定義されたら

#include <boost/spirit.hpp>

これだけでコンパイルエラーになるぞ(関数にこの名前が使われている)。

AssertMacros.h は、プリコンパイルヘッダにとりあえず突っ込まれる

#ifdef __OBJC__
    #import <Foundation/Foundation.h>
#endif

でインクルードされるから、このままではObjective-C++(.mm)でBoost.Spiritを使えない。
Spiritを使うコードを.cppに移すか、プリコンパイルヘッダで AssertMacros.h がインクルードされないようにするか、あるいは.mmの1行目で #undef check してしまおう。

C++から見たObjective-C - カテゴリ

タグ: C++ Objective-C

指定したクラスに、インターフェースやプロトコルに記されていないメソッドを好き勝手追加する機能。
これによって、「特定のメソッドを実装したインスタンスが要求される」場面で、既存のクラスのインスタンスを使えるようにできる。

void append10(id obj)
{
	for(int i = 0; i < 10; i++)
	{
		[obj append:i];
	}
}

@interface NSMutableString (Foo)
- (void)append:(int)n;
@end

@implementation NSMutableString (Foo)
- (void)append:(int)n
{
	[self appendFormat:@"%d", n];
}
@end

void hoge()
{
	NSMutableString* str = [NSMutableString stringWithCapacity:10];
	append10(str);
	NSLog(@"%@", str);
}

C++では、既存のクラスにメンバ関数を追加することなどできないので、「特定のメソッドが実装されていること」ではなく、「特定の関数(関数オブジェクト)の引数となれること」を要求する。

/// appendメンバ関数を持つクラス用
template <typename T>
void append(T& obj, int n)
{
	obj.append(n);
}

template <typename T>
void append10(T& obj)
{
	for(int i = 0; i < 10; i++)
	{
		append(obj, i);
	}
}

void append(std::string& obj, int n)
{
	obj += boost::lexical_cast<std::string>(n);
}

void hoge()
{
	std::string str;
	append10(str);
	std::cout << str << std::endl;
}

カテゴリは他にも、単にメソッドを判りやすく分割したり、外部に公開したくないメソッドをPrivateカテゴリに定義したり(公開していないだけで、呼べば応えるが)するためにも使われる(Objective-C 2.0には「無名カテゴリ」的な要素として「クラス拡張」なるものが存在している)。
色々面白い使い方が出来る機能だが、1つ不安な点がある。
それは、同じクラスに同じ名前のメソッドが複数定義されたとき、どれが呼ばれるかは分からないことだ。
だから、インターフェースだけ確認して「同じ名前のメソッドはないな」と追加したら、実はPrivateカテゴリのメソッドと名前がかぶっていて、どちらかが予期しない方を呼んで困ったことに?などといったことが起こり得る。

// Fooカテゴリと同名のメソッドを定義
// どちらが呼ばれるかは判らない
@interface NSMutableString (Bar)
- (void)append:(int)n;
@end

@implementation NSMutableString (Bar)
- (void)append:(int)n
{
	[self appendFormat:@"[%d]", n];
}

それを考えると、非公開カテゴリなんてものは、あまり作らないようにした方が良いのかもしれない。

C++から見たObjective-C - オーバーライド

タグ: C++ Objective-C

最近Objective-Cを触り始めたので、最も付き合いの長いC++と比べながら色々見ていってみる。
比べるといっても、C++にObjective-Cのような実行時解決能力を求める気はさらさらないので、解決のタイミングが実行時かコンパイル時かはここでは無視。
基本的には、Objective-Cは実行時解決、C++側はコンパイル時解決するものとして話を進める。

また、これは純粋な言語仕様の比較ではなく、あくまでも「『これを書いている時点での私』が使う上での比較」である。
単に「Objective-C」と書いているが厳密はこれは「Objective-C + Cocoaフレームワーク」であり、同様にC++も素のC++ではなく「C++ + Boost」である。

メソッドのオーバーライド

メソッドをオーバーライド/実装する時、わざわざインタフェース部で宣言しなくて良いのは嬉しい。

@protocol Foo
- (void)append:(int)n;
@end

@interface Bar : NSObject <Foo>
{
@private
	NSMutableArray* array;
}
// -(id)init;
// -(void)dealloc;
// -(void)append:(int)n などの宣言は不要
@end

@implementation Bar
- (id)init
{
	if(self = [super init])
	{
		array = [[NSMutableArray alloc] init];
	}
	
	return self;
}
- (void)dealloc
{
	[array release];
	[super dealloc];
}
- (void)append:(int)n
{
	[array addObject:[NSNumber numberWithInt:n]];
}
@end

C++はこれが地味に面倒。
仮想関数なら書かなくても良さそうなものだが、非仮想関数と書式が変わるのもそれはそれで鬱陶しいから仕方ない。

class Foo
{
public:
	virtual ~Foo() {}
	virtual void append(int n) = 0;
};

class Bar : public Foo
{
public:
	virtual void append(int n); // 1つだけならさして苦でもないが……
	
private:
	std::vector<int> array;
};

void Bar::append(int n)
{
	array.push_back(n);
}

この例だと、コンストラクタやデストラクタの分だけC++の方が楽に映るかな……

泉こなたコスプレver.

中に入ってた背景っぽいのを後ろに置いてカシャッ。

figma泉こなたコスプレver.

Appendix

タグ

Blog内検索

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。