2006年02月15日

Symbian(Series 60)上でのC++によるアプリケーション開発

Symbian OS上の代表的なAPIを用いて、Series 60上でアプリケーションを構築する際に必要な知識を習得します。
実際にSeries 60対応の携帯端末を使った環境で演習を実施します。
Symbianとは、Series60上での開発とはを効率よく学べる3日間です。
本コースはNokia(ノキア)社の認定トレーニングコースです。

期間

3日

時間

10:00〜17:00

定価

\189,000

前提条件

●C++で開発経験がある方
対象者

●Symbian OSおよびSeries 60上でのアプリケーション開発に興味をお持ちの方
 
コース内容

●SymbianとSeries 60
●Series 60 SDK
●Simbian OSの基本
●メモリ管理
●ディスクリプタ
●アプリケーション構造の概要
●リソースファイルとローカライズファイル
●UIコントロール
●クライアント/サーバフレームワーク
●アクティブオブジェクトフレームワーク
●Series 60 UI概要
●グラフィックス
●データ永続性
posted by シンビアン at 16:34| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

2006年02月14日

Sony Ericsson、Symbian採用のスマートフォンにメモリースティックDuo搭載

Sony Ericsson Mobile Communicationsは、すでにアナウンス済みのGSM携帯電話「P800」に、メモリースティックDuo(以下Duo)スロットを搭載することを発表した。

P800は、3月にすでに発表されていたもので、今回、詳細なスペックが明らかになった。大きな特徴はDuoに対応した点。日本でもDuo対応端末が7月に登場することが明らかにされているが、正式なアナウンスとしては「世界初ということで間違いない」(同社日本法人広報)。容量は16MBのものが同梱され、PCとの各種データ交換や端末のデータをバックアップすることができる。MP3プレイヤー機能も搭載されており、Duo、または端末に保存した MP3ファイルを再生して、付属のステレオイヤホンで楽しむことができる。

デジタルカメラも内蔵されており、撮影画像やPCから転送した画像データをスクリーンセーバーとして利用したり、フォトライブラリーに保存したりできる。内蔵メモリは12MBで、付属の16MBのDuoと合わせ、28MBのユーザーメモリが提供され、十分にデータを保存できる。データの転送は、 Duoだけでなく、Bluetooth、赤外線、ケーブル、と多彩な方式に対応している。

Sony Pictures Digital Entertainmentが提供する、7月公開の「Men In Black II」のゲームが楽しめるのも特徴。映画に登場する武器でエイリアンを倒す射撃ゲームだ。

OSにはSymbian OS v7.0を搭載、UIQと呼ばれるペンで操作するインタフェースを採用していたPDAタイプの端末だ。ストリーミングのメディア再生にも対応、Wordや ExcelといったMicrosoft Officeのデータの編集・閲覧もできる。BluetoothによるPCとのデータ同期も可能だ。

Webは、HTML、XHTML、cHTML、WAPなどの表示に対応し、欧米やアジアにおけるGSM 900/1800/1900のエリアで利用できる。また、中国語バージョンの「P802」も提供される。発売はともに第3四半期の予定。残念ながらGSM 方式のため日本では利用できない。同社日本法人は同様の端末の開発予定はない、とするが、Symbian OS v7.0はCDMA方式にも対応していることもあり、日本での開発も期待したい。
posted by シンビアン at 20:11| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

2006年02月13日

非同期の処理

例えば、Series60を用いてネットワークを用いたサーバアプリケーションを構築するとしましょう。この際、サーバはクライアントからの接続を待つ必要があります。この「接続を待つ」という行為は明確なタイミングがわかっているものではありません。このような場合、通常は別にスレッドを立て、クライアントからの接続を監視させる事を考えると思います。

また、同一アプリケーションで何らかの時間のかかる処理を走らせている間もユーザーの入力は受け付けなくてはいけないという状況はあるはずです。このような場合も普通に考えるのであれば別にスレッドを立てると思います。今回は、上記のような状況を一般に「非同期の処理」と呼び、その処理をSeries60 上で実現する方法を見てみたいと思います。

マルチスレッドは使用しない?

 Series60では、マルチスレッドを用いたプログラムが可能になっております。しかし、ここで紹介する方法は、マルチスレッドの使用方法を説明するものではありません。スレッドはあくまでもメインのスレッド1本です。非同期の処理を実現するのにスレッドを用いないのは「Series60ではマルチスレッドを用いたプログラムが推奨されない」点に理由があります。マルチスレッドを用いることが推奨されない理由としては

* コンテキストスイッチの切り替えなど、マルチスレッドはリソースを多く消費する
* デットロックの回避等、マルチスレッド特有の問題に対処しなくてはならない

などが挙げられます。

 推奨しないからには、スレッドに変わる非同期の処理を行う機構が必要です。これが「アクティブオブジェクト」と「アクティブスケジューラ」になります。

アクティブオブジェクトとアクティブスケジューラ

 アクティブオブジェクトとアクティブスケジューラは、アプリケーションフレームワークの中で使用されるクラスです。各々の詳細を挙げると、

アクティブスケジューラ
各イベントに応じてアクティブオブジェクトのスケジューリングを行う。アクティブスケジューラ は1つのスレッドにつき1つのみ存在できる。
アクティブオブジェクト
アクティブスケジューラから呼び出され、非同期の処理を行う実体であり、コールバックの関数としてRunL()関数を持つ。

となります。

 つまり「アクティブスケジューラは簡単なスケジューリングを提供する。スケジューリングの対象がアクティブオブジェクトになる」と見ることができます。さらに言えば、「Series60のアプリケーションは、SymbianOSが提供するプロセスごとのスケジューリングと同時に、アプリケーションフレームワーク内部でも各スレッドごとにアクティブスケジューラが行うスケジューリングがある」ということができます。
posted by シンビアン at 21:03| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

2006年02月11日

仮想関数について

純粋仮想関数

基本クラスでは,単に主要なメンバ関数と変数を宣言し,必要なものは派生クラスで用意することが多い.すなわち,基本クラスのオブジェクトに対する操作は行わず,これを継承した派生クラスのオブジェクトに対して操作を行うことが多い.

この場合には,基本クラスには仮想関数を使用するための名前だけで実態のないメンバ関数を用意しておけばよいことになる.このとき,純粋仮想関数を使用する.

純粋仮想関数とは,関数定義を持たず,次のような形式をしている.

virtual 型名 関数名(引数リスト) = 0;

この定義では,関数本体が 0 に等しいと定義している.すなわち,関数本体が存在しないことを表す.

このような関数を持つクラスを継承する派生クラスでは,実体のない関数を呼ぶことができないため,必ずその関数を再定義しなおさなければならない.

少なくとも 1 つの純粋仮想関数を含んでいるクラスを 抽象クラス と呼ぶ.抽象クラスは,本体のない関数を含んでいるため,そのクラスのオブジェクトを作成できない.抽象クラスへのポインタは作成でき,実行時のポリモーフィズムは実現できる.

class date {
int y, /* year */
m, /* month */
d; /* day */
public:
virtual bool leapyear() = 0; /* 純粋仮想関数 */
int getyear() { return y; }
};

class seireki: public date {
public:
bool leapyear() { /* 閏年 */
return (getyear() % 400 == 0)
|| (getyear() % 4 == 0) && (getyear() % 100 != 0);
} /* 純粋仮想関数を持つクラスを継承しているクラスでは,
必ず再定義する */
};

class heisei: public date {
public:
bool leapyear() { /* 閏年 */
return ((getyear() + 1988) % 400 == 0)
|| ((getyear() + 1988) % 4 == 0)
&& ((getyear()+ 1988) % 100 != 0);
} /* 純粋仮想関数を持つクラスを継承しているクラスでは,
必ず再定義する */
};

上の例では, leapyear は基本クラス date の中で純粋仮想関数として定義されている.よってクラス date は抽象クラスとなりそのオブジェクトは作成できない.

西暦のクラスと平成のクラスを date の派生クラスとして定義しており,それぞれで閏年かどうかを判定する関数 leapyear を再定義している.

よって,

int main()
{
date *p;
heisei nen;
seireki year;

...
date = &nen;
if (date->leapyear()) {
...
}
date = &year;
if (date->leapyear()) {
...
}
}

のように記述できる.
posted by シンビアン at 08:14| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

2006年02月05日

参照カウント

参照カウント(さんしょうカウント)は、ガベージコレクタの動作方法の一つ。

* すべてのオブジェクト(メモリ上におかれているデータの単位)に対して、参照カウントと呼ばれる整数値を付加しておく。これは、このオブジェクトへのポインタがシステム全体にいくつ存在しているかを数えるものである

* オブジェクトへの参照が変化するたびにこの値は随時書き換わる。
* 参照カウントが0になったものについては破棄が許される。

この方法は処理が高速であり、オブジェクトを多数生成し、すぐに参照を切るような処理においても迅速に破棄が起きる。ただ、不要になったオブジェクト同士が循環参照してる場合、参照カウントが0にならないために破棄がおきないという問題がある。

また、参照カウントが頻繁に書き変わる場合には書き換え負荷そのものが問題になる場合もある。さらに、単純な実装の場合、大量のオブジェクトが一斉に解放になる場合があり、 CPUの空き時間を利用してガベージコレクションを行う方法と比べると、メモリの解放で処理が遅くなってしまう場合もある。

class A {
public B b;
}
class B {
public A a;
}
public class Test{
public static void main(String[]arg) {
A a=new A(); // *1*
a.b=new B();
b.a=a;
a=null;
// *1*で作成したAのオブジェクト は到達不可能にもかかわらず、参照カウントは1
}
}

例2

ウィキペディアの「孤立した記事」は、参照カウントが0のものを表示しているだけなので、孤立した記事だけから参照されている記事は孤立した記事と見なされていない。

上で述べた問題を回避する方法としてマーク・アンド・スイープがある。
[編集]

実用例

* マイクロソフトのComponent Object ModelにおけるCOMオブジェクトは参照カウント方式で管理される。
* プログラミング言語Pythonのガベージコレクタは主に参照カウント方式を用いている。
* Boost C++ Library の smart_ptr
posted by シンビアン at 08:42| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

参照カウンタ

概要

アクセス不可能オブジェクトを決定する方法の1つに、参照カウンタ (reference counting) を使う方法があります。これは非常にシンプルな GC で、実装が容易であるが、後で述べるように致命的な欠点があるため、通常は他の GC 手法と併用する必要がある。

【注意】参照カウント法を GC 手法と見なさない人々もいる。

実装

1. GC 管理されるすべてのオブジェクトに、参照カウンタを付随させます。
2. オブジェクトへの参照が作成されると、参照カウンタを 1 つ増やします。
3. オブジェクトへの参照が削除されると、参照カウンタを 1 つ減らします。
4. 参照カウンタを減らしたときに参照カウンタが 0 になれば、 オブジェクトを回収する。

サンプル

次のような擬似 Java コードを考えてみよう。

SomeObject obj1, obj2, obj3;

// ここで作成されるオブジェクトは、作成と同時に変数にバインドされるので、
// 参照カウントが 1 になる。
obj1 = new SomeObject("A"); // オブジェクト A を作成
obj2 = new SomeObject("B"); // オブジェクト B を作成

// オブジェクト A は obj3 変数により参照されるので、参照カウントが増えて 2 になる。
obj3 = obj1;

// オブジェクト A は obj2 により参照されるので、参照カウントが 3 になる。
// それと同時にオブジェクト B の参照は減らされて 0 になり、
// オブジェクト B は回収されることになる。
obj2 = obj3;

利点
GC 実行時間が極めて短い

オブジェクトの回収はオブジェクト毎に行われるため、GC の実行時間はきわめて短い。

そのため、リアルタイム用途に適しています。
欠点
参照カウンタの増減によるオーバーヘッド

オブジェクトの大半は寿命が非常に短いという一般法則があります。参照カウント法では、すぐに不要になるオブジェクトに対しても参照カウントを増減させるというオーバーヘッドが伴います。

このようなオーバーヘッドはコンパイラによる最適化によって大きく軽減できる可能性があります。

[執筆注: もう少し精密な議論ができるはず。]
オブジェクト毎に参照カウンタ用のストレージが必要

十分な大きさの参照カウントを保持しようとすると、そのためのストレージのオーバーヘッドが大きくなる (通常の 32 bit システムでは、参照カウントを 32 bit にとることが多いであろう。) また、参照カウンタようのストレージを小さく取ると、次に述べるオーバーフローの問題が出てくる。
参照カウンタのオーバーフローの問題

たとえば、参照カウント用に 8 bit のストレージを使うとすると、参照カウントは 255 までしか使えない。参照カウントのオーバーフローを許すと、実際には 256 の参照があるにも関わらずオブジェクトが回収されてしまうことが起こりかねない。

このような問題を避けるため、参照カウントの最大値に達したオブジェクトに対しては、もはやカウントの増加も減少も行わないように、参照カウントのロックが行われる。これにより、参照されているオブジェクトが回収されてしまうという問題を回避することはできるが、ロックされたオブジェクトは永遠に回収されないという問題が起きる。

ロックされたオブジェクトを回収するためには、参照カウント法を別の GC 手法と組み合わせることが多い。
循環参照を行うオブジェクトを回収できない

2 つのオブジェクトが互いに相互参照を行う場合、両オブジェクトが不要になっても参照カウントが 0 にならないため、決して回収されない。

注意すべきは、相互参照をしている当該のオブジェクトだけでなく、そのオブジェクトから直接・間接に参照されているオブジェクトも、決して回収されないことである。

この問題は参照カウント GC だけでは解決することは不可能であり、別の GC 手法を併用することで回収を行う。
サンプル

次の Java コードでは、Mike と Cathy は相互参照をしており、両オブジェクトが不要になった後でも参照カウントは 0 にならない。

obj1 = new Person( "Mike" );
obj2 = new Person( "Cathy" );
obj1.wife = obj1;
obj2.husband = obj2;
obj1 = null;
obj2 = null;
posted by シンビアン at 08:40| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

2006年02月03日

4種類の新しいキャスト

キャストは使わずに済ませられるならそれに越したとはないのですが、どうしても使わざるを得ないシチュエーションは少なからず存在します。

ならばせめて少しでも安全にキャストするために新たに定められたキャスト構文が、以下に挙げる static_cast / reinterpret_cast / const_cast / dynamic_cast です。

* static_cast

static_cast(expr)

static_castはexprの型からtypeへの暗黙の型変換、あるいはtypeからexprへの暗黙の型変換が存在する場合にだけキャストします。キャスト不可能であればコンパイルエラーとなります。

// int から long へ
int ival;
long lval = static_cast(ival);

reinterpret_cast

reinterpret_cast(expr)

reinterpret_castはtype(expr)が許されるなら、exprをtypeに単にキャストします。

// long から int* へ
long lval;
int* iptr = reinterpret_cast(lval);

reinterpret_castは単なる型変更であり、たとえ派生関係があったとしてもポインタのアドレス自体はキャスト前と変わりません。
その意味でreinterpret_castは非常に危険なキャストといえるでしょう。

const_cast

const_cast(expr)

const_castはconstおよびvolatile修飾子を無効にするだけのキャストを行ないます。そのほかのときはコンパイルエラーとします。

// const int* から int* へ
const int* ciptr;
int* iptr = const_cast(ciptr);

dynamic_cast

dynamic_cast(expr)

dynamic_castは基底クラスへのポインタ(or 参照)から派生クラスへのポインタ(or 参照)への型保証キャストを行ないます。
上記3種のキャストはコンパイル時にキャストしますが、dynamic_castは実行時に型の検査が行なわれ、変換不可能であれば0を返します。



class Base { ... };
class Derived : public Base { ... };

Base base;
Derived derived;

Derived* pd1 = dynamic_cast(&base); // 失敗 pd1 == 0
Derived* pd2 = dynamic_cast(&derived);


参照のキャストに失敗すると、std::bad_cast例外がthrowされます。

try {
Derived& rd1 = dynamic_cast(base);
} catch ( const std::bad_cast& e ) {
std::cout << e.what() << std::endl;
};

dynamic_castにより、従来のキャストでは不可能であった クロス・キャスト、そして抽象基底クラスからのダウン・キャストが可能になりました。

*
o クロス・キャスト

/*
* Shape と Drawable は派生関係にない
*/
class Shape {
...
};

class Drawable {
public:
virtual void draw() =0;
};

/*
* Circle は Drawable にキャスト可能
*/
class Circle : public Shape, public Drawable {
public:
virtual void draw();
};

Shape* shape = new Circle;
/* 従来のキャスト (Drawable*)shape では、
* 場合によっては暴走する */
Drawable* drawable = dynamic_cast(shape);
if ( drawable )
drawable->draw();


o 抽象基底クラスからのダウン・キャスト

class Machine {
...
};

class TV : virtual public Machine {
public:
void tune(int channel);
};

class VTR : virtual public Machine {
public:
void record();
};

class TeleVideo : public TV, public VTR {
...
};

Machine* machine = new TeleVideo;
/* 従来のキャスト (TeleVideo*)machine はコンパイルエラー */
TeleVideo* televideo = dynamic_cast(machine);
if ( televideo ) {
televideo->tune(8);
televideo->record();
}



※注意
dynamic_castが適用できるのはポリモルフィック・クラス、 すなわち少なくとも一つのメンバ関数が仮想関数でなくてはなりません。

struct non_polymorpic { ... }; // 仮想関数が存在しない
struct something : non_polumorphic { ... };
non_polymorphic* p;
something* q = dynamic_cast(p); // error!

新しいキャストは従来のキャストよりはるかに安全です。

また、grepなどで"_cast"をキーにすればキャストした個所を簡単に検索できるという副次的な効果もあります。
posted by シンビアン at 16:44| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

従来のキャストの問題点

異なる型への変換において、C/C++ではキャストが用いられます。

// intからlongへのキャスト
int ival;
int lval = (long)ival;

ご存知のとおり、キャストは非常に危険です。 本来ならば型の不一致によるコンパイルエラーをねじ伏せるのですから。

キャストの使われ方(意味)は、大きく3種(型変換/型変更/const外し)に分類されます。

1. 型変換

// int から double へ
int ival;
double dval = (double)ival;

2. 型変更

// long から int* へ
long lval;
int* iptr = (int*)lval;

3. const外し

// const int* から int* へ
const int* ciptr;
int* iptr = (int*)ciptr;

上記のどの目的でキャストしても、構文としてはどれも同じ

(型)式

です。つまりコードからは"何が目的でキャストしたのかはっきりしない"のですね。
posted by シンビアン at 16:38| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

2006年02月01日

Symbian OS C++ 実践開発技法

第1章 Symbian OSにおけるクラス名の規約

1.1 基本型
1.2 Tクラス
1.3 Cクラス
1.4 Rクラス
1.5 Mクラス
1.6 静的クラス
1.7 ご注意ください
1.8 まとめ

第2章 リーブ:Symbian OSの例外

2.1 リーブする関数
2.2 new(ELeave)を使うヒープ割り当て
2.3 コンストラクタとデストラクタ
2.4 リーブする関数の扱い方
2.5 TRAPとTRAPDを使ってリーブをトラップする
2.6 LeaveScan
2.7 まとめ

第3章 クリーンアップスタック

3.1 クリーンアップスタックを使う
3.2 クリーンアップスタックの仕組み
3.3 非CBaseクラスでクリーンアップスタックを使う
3.4 カスタマイズしたクリーンアップのためにTCleanupItemを使う
3.5 可搬性
3.6 キャストの使用に関する注意事項
3.7 まとめ

第4章 2フェーズコンストラクション

4.1 まとめ

第5章 ディスクリプタ:Symbian OSの文字列

5.1 書き換え不可能なディスクリプタ
5.2 書き換え可能なディスクリプタ
5.3 ポインタディスクリプタ
5.4 スタック上のバッファディスクリプタ
5.5 ヒープ上のバッファディスクリプタ
5.6 リテラルディスクリプタ
5.7 まとめ

第6章 ディスクリプタの正しい使い方

6.1 パラメータや戻り値の型としてのディスクリプタ
6.2 ディスクリプタの共通メソッド
6.3 ヒープディスクリプタHBufCの使い方
6.4 ディスクリプタの外部化と内部化
6.5 TFileNameの使いすぎ
6.6 ディスクリプタの操作に便利なクラス
6.7 まとめ

第7章 動的な配列をバッファ

7.1 CArrayXクラス
7.2 RArrayとRPointerArray
7.3 CArrayXではなくRArrayを使う理由
7.4 動的ディスクリプタ配列
7.5 固定長配列
7.6 動的バッファ
7.7 まとめ

第8章 アクティブオブジェクトによるイベント駆動のマルチタスク

8.1 マルチタスクの基礎
8.2 イベント駆動のマルチタスク
8.3 アクティブオブジェクトの扱い方
8.4 サンプルコード
8.5 アクティブスケジューラを持たないスレッド
8.6 アプリケーションコードとアクティブオブジェクト
8.7 まとめ

第9章 アクティブオブジェクトの実際

9.1 アクティブオブジェクトの基本
9.2 アクティブオブジェクトの役割分担
9.3 非同期サービスプロバイダの役割分担
9.4 アクティブスケジューラの役割分担
9.5 アクティブスケジューラの起動
9.6 アクティブスケジューラのネスト
9.7 アクティブスケジューラの拡張
9.8 キャンセル
9.9 要求の完了
9.10 ステートマシン
9.11 長期タスク
9.12 CIdleクラス
9.13 CPeriodicクラス
9.14 よくある間違い
9.15 まとめ

第10章 Symbian OSのスレッドとプロセス

10.1 RThreadクラス
10.2 スレッドの優先順位
10.3 実行中のスレッドを停止させる
10.4 スレッド間のデータ転送
10.5 例外処理
10.6 プロセス
10.7 まとめ

第11章 クライアント/サーバフレームワークの理論

11.1 なぜクライアント/サーバーフレームワークを持たせるのか?
11.2 クライアントとサーバは、どのように連携するのか?
11.3 クライアントとサーバは、どのように通信するのか?
11.4 クライアント/サーバフレームワークで使われるクラスは?
11.5 同期要求と非同期要求の違いは?
11.6 サーバに起動方法は?
11.7 1つのクライアントは何個の接続を持てるのか?
11.8 クライアントが接続を断つとどうなるのか?
11.9 クライアントが死ぬとどうなるのか?
11.10 サーバが死ぬとどうなるのか?
11.11 クライアント/サーバ通信はスレッドをどう使うか?
11.12 サーバがわにアクティブオブジェクトがある意味は?
11.13 ローカル(同一プロセス)サーバの利点は?
11.14 クライアント/サーバ通信のオーバーヘッドには何があるのか?
11.15 クライアントがサーバに出せる未解決要求は何個までか?
11.16 サーバ機能は拡張できるのか?
11.17 サンプルコード
11.18 まとめ

第12章 クライアント/サーバフレームワークの実践

12.1 クライアント/サーバの要求コード
12.2 クライアントのボイラープレートコード
12.3 サーバを起動し、クライアントからそれに接続する
12.4 サーバのスタートアップコード
12.5 サーバクラス
12.6 サーバのシャットダウン
12.7 サーバをアクセスする
12.8 まとめ

第13章 各種バイナリファイル

13.1 Symbian OSのEXE
13.2 Symbian OSのDLL
13.3 書き換え可能な静的データ
13.4 TLS(スレッドローカル記憶域)
13.5 DLLローダ
13.6 UID
13.7 targettype指定子
13.8 まとめ

第14章 ECOM

14.1 ECOMアーキテクチャ
14.2 ECOMインターフェイスの特徴
14.3 ファクトリメソッド
14.4 ECOMインターフェイスを実装する
14.5 リソースファイル
14.6 クライアントコードのサンプル
14.7 まとめ

第15章 パニック

15.1 「ジャストインタイム」デバッグ
15.2 正しいパニックの形式
15.3 Symbian OSにおけるパニックの分類
15.4 別スレッドにパニックを発生させる
15.5 フォールトとリーブとパニック
15.6 まとめ

第16章 アサートを使ってバグを検出する

16.1 __ASSERT_DEBUG
16.2 __ASSERT_ALWAYS
16.3 まとめ

第17章 デバッグマクロとテストクラス

17.1 ヒープチェック用マクロ
17.2 オブジェクト不変マクロ
17.3 RTestを使ったコンソールテスト
17.4 まとめ

第18章 互換性

18.1 前方互換性と後方互換性
18.2 ソース互換性
18.3 バイナリ互換性
18.4 互換性を守る方法
18.5 バイナリ互換性を守りながら何を変更できるのか?
18.6 最良の策は、将来の変更に備えること
18.7 互換性とSymbian OSのクラス型
18.8 まとめ

第19章 薄いテンプレート

19.1 まとめ

第20章 包括的で分かりやすいAPIを公開しよう

20.1 クラスのレイアウト
20.2 IMPORT_CとEXPORT_C
20.3 パラメータと戻り値
20.4 メンバデータと「関数による抽象化」
20.5 クラス名、メソッド名、パラメータ名の選択
20.6 コンパイラが生成する関数
20.7 まとめ

第21章 正しいコーディングスタイル

21.1 プログラムコードのサイズを削減する
21.2 ヒープメモリを注意して使う
21.3 スタックメモリを注意して使う
21.4 コードの効率を最大化するために共通部分式を削除する
21.5 最適化は最後の手段
21.6 まとめ

付録

コードチェックリスト
クラスの宣言
ヘッダファイル
コメント
コンストラクタ
デストラクタ
割り当てと削除
クリーンアップスタックとリーブ安全性
ループとプログラムのフロー制御
プログラムのロジック
ディスクリプタ
コンテナ

用語集

参考文献とオンラインリソース
Symbian OSに関する書籍
C++に関する書籍
インターネット上のSymbian OSリソース
posted by シンビアン at 21:55| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

継承(「is a」の関係)

継承とは、あるクラスが別のクラスの「特化」であるという概念だ。継承の目的は、複数の派生(サブ)クラスの共通要素を規定する基底(スーパー)クラスを定義して、コードを単純にすることである。共通要素としては、ルーチンのインターフェイス、実装、メンバデータ、データ型などが挙げられる。継承を利用すれば、コードやデータを基底クラスで一元的に管理できるようになるので、複数の場所で定義する必要がなくなる。継承を使用する際には、次の点について決断する必要がある。

*

メンバルーチンはそれぞれ派生クラスから参照できるか。既定の実装を持つか。既定の実装はオーバーライド可能か。
*

メンバデータ(変数、名前付き定数、列挙など)はそれぞれ派生クラスから参照できるか。
posted by シンビアン at 20:50| Comment(0) | TrackBack(1) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

プライベート継承を使って「has a」を実装するのは最後の手段である

状況によっては、あるオブジェクトを別のオブジェクトのメンバにする方法では、包含を実現できないことがある。そのような場合、一部の専門家は、包含されるオブジェクトをプライベートで継承することを提案している(Meyers1998; Sutter 2000)。そうする主な理由とは、含む側のクラスを含まれる側のクラスのprotectedのメンバルーチンまたはメンバデータにアクセスさせるためである。実際には、これでは祖先のクラスとの結合度が強くなりすぎて、カプセル化に違反してしまう。たいていは、プライベート継承以外の方法で解決しなければならない設計上のミスである。
posted by シンビアン at 20:39| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

確保したヒープメモリの有効範囲

質問:

[PIMInfo->PIMAplWork]に設定したヒープはOUT中でも保持されるとの事ですが、 それ以外のポインタ(外部変数)等に確保したヒープは保持されないのでしょうか?サンプルで実験したところでは、問題なく保持されているようです。

PTR pDummy;

BOOLEAN PIMAplConstruct(PIMINFO *PIMInfo, USHORT InitLevel)
{
/* OUTワークの確保 */
if (!(pDummy = NewHeapPtr(1000)))
{
/* ヒープが確保できない場合は − 続行不可能 */
return false;
}
return true;
}

この様な方法で確保したヒープメモリが保持できない理由と、メモリの内容が破壊されるタイミングを教えて欲しい。

回答:

ヒープ領域の有効範囲については、確保した関数は関係ありません。ヒープ領域自体はアプリケーションプロセスが起動している間中、保持されます。従いまして、確保した領域へのハンドル、またはポインタを格納する変数に依存します。
PIMInfo->PIMAplWorkは、アプリケーションプロセスが起動している間中、保持されます。
PIMExecWork->PIMUserWorkは、アプリケーションがアクティブな間中、保持されます。アプリケーションがOUT状態になった時点で値は保証されなくなります(通常はOUT状態に移行する前にユーザが解放する必要がありますが、仮に解放しなくても値は保証されません)。
AplForm->UserWorkはフォームが表示されている間中、保持されます。他のフォームに切り替わった時点で値は保証されなくなります(通常はフォームが切り替わる前にユーザが解放する必要がありますが、仮に解放しなくても値は保証されません)。
その他の、例えばお客様がご指摘されているような外部変数についてはC言語の仕様に準じます。つまり外部変数の場合は、アプリケーションプロセスが起動している間中、保持されます。
posted by シンビアン at 20:12| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

ヒープメモリについて

1)PIMクラスメソッドにおいて確保したヒープメモリの有効範囲
PIMAplSetupにおいてNewHeapPtrで確保したヒープメモリは、アプリケーション上でいつでも使用する事が可能か? 一度OUT状態になっても再びEXEC状態に戻れば、ヒープメモリが開放されたりメモリの内容が破壊されたりする事は無いと考えて良いか?

(2)この様にアプリケーションが常時使用するヒープメモリは、PIMAplConstructとPIMAplSetupのどちらで確保した方が良いのか?

(3)PIMクラスメソッドにおいて確保したヒープメモリの開放処理
 PIMAplConstruct又はPIMAplSetupで確保したヒープメモリを開放する場合は、PIMAplEndで行う必要があるか? それともOSに任せて、開放処理を行わない方が良いのか?

回答:

(1)OUT中でも保持されています。
ここでいうOUT中も保持されるヒープとは[PIMInfo->PIMAplWork]に設定したヒープです。

(2)どちらでも結構ですが、プログラミングガイド第8章のサンプルでは、PIMAplConstruct関数で確保しています。

(3)これらの関数内で確保すべき、OUT中も保持されるヒープの解放は、アプリ終了時にシステムが行います。ユーザーが解放する必要はありません。なお、ここでいうOUT中も保持されるヒープとは[PIMInfo->PIMAplWork]に設定したヒープです。


アプリケーションで使用する上記のようなヒープは、大まかに3種類あります。下記以外にも局所的に使用するワークとしてのヒープもあります。

・OUT中も内容が保持されるヒープ
 PIMAplConstruct または PIMAplSetUp関数で[PIMInfo->PIMAplWork]に設定したヒープです。主にレジュームするための情報を保持しておきます。ユーザーが解放処理をする必要はありません。

・EXEC中に保持されているヒープ(OUT中は不明)
 PIMExecConstruct または PIMExecSetUp関数で[PIMExecWork->PIMUserWork]に設定したヒープです。主に複数のフォーム間で共有する情報を保持しておきます。ユーザーがPIMExecEnd関数で解放処理を行います。

・フォーム実行中に保持されているヒープ(フォーム終了後は不明)
 FormConstruct または FormSetUp関数で[AplForm->UserWork]に設定したヒープです。主にフォーム実行中に参照したい内容を保持しておきます。ユーザーがFormEnd関数で解放処理を行います。
posted by シンビアン at 20:11| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

ヒープメモリ

Microsoft IEで不正なCSSタグを解析するとヒープメモリがクラッシュする脆弱性が見つかる
サイバーディフェンス社からの情報によると、マイクロソフト社のInternet Explorerの複数のバージョンで、リモートから攻撃可能なバッファオーバーフローが見つかった。これにより、ローカルユーザの権限で任意のコードが実行される可能性がある。この問題は、CSS(Cascading Style-Sheets)を処理する際の不正なバッファが原因で発生する。@;/*というCSSスタイルタグを含んだ不正なwebページを表示させられると、脆弱なバージョンのIEがクラッシュする可能性がある。この脆弱性により、任意のコードが実行される可能性がある。
posted by シンビアン at 20:10| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

2006年01月31日

第13章 constオブジェクト

今回は、constオブジェクトについて解説します。 constとは、読んで字のごとく「定数」という意味です。これは、勝手に値を変更できないことを意味しています。したがって、オブジェクトをconstで宣言するとデータメンバは、一切変更できなくなります。それどころかメンバ関数も一切呼び出せなくなります。もしかすると、メンバ関数が値を書き換えようとするかもしれないからです。
エー、そりゃ大変だな・・・
ま、例題プログラムを見てみましょう。

#include class Test { public: int a; Test(); ~Test(); int show(); }; Test::Test(void) { cout << "数値を入力-- "; cin >> a; } Test::~Test() { cout << "デストラクタが呼ばれました。\n"; } int Test::show(void) { cout << "[show] a = " << a << endl; return 0; } int main(void) { const Test x; cout << "[main] x.a= " << x.a << endl; //x.a = 20; エラーとなります。 //x.show(); 呼び出せません。 Test y; cout << "[main] y.a= " << y.a << endl; y.a += 12; //constオブジェクトでないので大丈夫 y.show(); cout << "値を変更しました。y.a = " << y.a << endl; return 0; }

オブジェクトxはconst宣言をしているのでデータメンバa に、値を代入することはできません。また、show関数を呼び出すこともできません。

一方、オブジェクトbは、const宣言をしていませんからデータオブジェクトに代入したり、show関数を呼び出したりできます。

当たり前ですがconst宣言をしていてもデストラクタとか、コンストラクタは自動的に呼ばれます。

いくらconstオブジェクトでも、値を書き換えないようなメンバ関数まで呼び出せないのはつらい?ですね。そこで、メンバ関数の宣言時に関数の最後にconstと付け足してやります。こうすれば呼び出すことができます。また、このconstは関数定義の所でも必ず記述しなくてはいけません。ではサンプルプログラムを見てみましょう。

#include class ConstTest { public: int a; ConstTest(); void show() const; //constの位置に注意 }; ConstTest::ConstTest(void) { a = 100; } void ConstTest::show(void) const //ここにもconst必要! { cout << "show関数が呼ばれました" << endl; cout << "a = " << a << endl; return; } int main(void) { const ConstTest x; x.show(); return 0; }

はい、左のようにshow関数を呼び出すことができました。では、このshow関数中にこっそり値を書き換えるような記述をしたらどうでしょうか?
何で、そんな変なことをするんだ!?
はい。ばかばかしいことでも実際に実験してみることが大事です。

error C2166: const型で宣言された項目を修飾しようとしました。

筆者の処理系では上のようなエラーメッセージが出てコンパイルできません。みなさんの処理系ではどうですか?
posted by シンビアン at 19:28| Comment(1) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

開発環境をそろえる

SDKの入手


FORUM NOKIAのDeveloper GuideからツールSDKダウンロードを選んでSeries 60 SDK (ソフト開発キット) for Symbian OS Nokia Edition (英語)をクリックします
ここで、2nd ed.FP 2,japanies(141MB)を選んでDownload nowを押すとForum Nokiaのアカウント(IDとパスワード)を聞いてきますのでID,パスワードを入れてください。
このアカウントはthe registration form から登録できます。これは、簡単なアンケートです。
アンケート後に送られて来たメールでレジストしてください。
日本語版と英語版のメールが来ますが、英語版が来るまでダウンロードは出来ません。
あとは、別ウインドウでダウンロードが始まりますので、終了するのを待ってください。(私の環境ではダウンロード後にIEが異常終了しましたが、ダウンロードはちゃんとできました。

Perlの入手


このSDKではPerlが必要ですので、ActivePerlをインストールしておきます

JRE1.4の入手


無くてもコンパイルは出来ますが、ツールが一部使えませんのでJava Run-Timeの1.4以降をインストールして置いてください。

SDKのインストール


画面の指示に従ってインストールしてください。最後に再起動があります

VC,BloandC++を使わないでコンパイル


実は、エミュレータを使わなければこのIDE環境は不要です。
いきなり実機でのテストになりますが

HelloworldBasicをコンパイルしてみる


* Windowsのコマンドラインを開きます
* HelloworldBasicのディレクトリーにいどうします
CD \Symbian\8.0a\S60_2nd_FP2_J\Series60Ex\helloworldbasic\group
* ビルドの準備を行います
bldmake bldfiles
* ビルドします
abld build THUMB UREL
* ビルドが終了すると¥SISフォルダにpkgファイルがファイルができますので、SISファイルに変換します
CD \Symbian\8.0a\S60_2nd_FP2_J\Series60Ex\helloworldbasic\sis
makesis helloworldbasic.pkg
* これでSISファイルができましたので、超勝手アプリのインストールと同じように702NKへインストールします。

※ちなみに\Symbian\8.0a\S60_2nd_FP2_J\epoc32\BUILDの下にMakeファイル、オブジェクトが作成されますので気をつけてください

AIFのアイコンを変更するときは一度、\epoc32\data\Z\system\APPS\アプリケーション名\の下のAIFファイルを消して、再度BUILDします。
これをしないとアイコンが変更されません

これで何とか、開発できる状態になりましたので、実際のプログラムの構成を見て行きたいと思います。
posted by シンビアン at 18:31| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

リファレンスの初期化

ここで、リファレンスの初期化についてもう少し詳しく見ていくことにしましょう。

まず、const でない T 型のリファレンスを初期化する場合ですが、この場合の右辺には以下のように T 型の左辺値だけが許されます。

001 int test();
002
003 void f()
004 {
005 int i;
006 unsigned int ui;
007 long l;
008
009 int& r1 = i; // OK. i は int 型の左辺値。
010 int& r2 = r1; // これも OK.
011 int& r3 = ui; // error. unsigned int は int じゃない。
012 int& r4 = l; // error. long も int じゃない。
013 int& r5 = test(); // error. 一時変数は左辺値じゃない。
014 int& r6 = 3; // error. 定数は左辺値じゃない。
015 }

ちなみに、左辺値かどうかは、値を代入することができるかどうかで判断します。13,14行目のような一時変数や定数は値を代入することができませんので、それらは左辺値でないことが分かります。

const でない T 型のリファレンスを初期化できるのは、T 型の変数か const でない T 型のリファレンスだけでした。それに対し、const T& は様々な値で初期化することができます。T 型に変換することができれば T 型でなくてもかまいませんし、一時変数や定数でもかまいません。

001 int test();
002
003 void f()
004 {
005 int i;
006 unsigned int ui;
007 long l;
008
009 const int& r1 = i; // OK.
010 const int& r2 = r1; // OK.
011 const int& r3 = ui; // OK.
012 const int& r4 = l; // OK.
013 const int& r5 = test(); // OK.
014 const int& r6 = 3; // OK.
015 }

また、13行目の r5 にはテンポラリオブジェクトの寿命に関する興味深い点があります。test() の戻り値のようなテンポラリオブジェクトの寿命は、式の終わり(つまり13行目のセミコロン)までとなっているはずです。ところが r5 は14行目以降でも問題なく使うことができます。つまり、テンポラリオブジェクトを const なリファレンスで参照することによって、そのテンポラリオブジェクトの寿命を延ばすことができるのです。この点に関しては、「const なリファレンスの初期化によるテンポラリオブジェクトの延命」にて詳しく説明したいと思います。

最後に、const なリファレンスを初期化できない例として、volatile の使用を挙げておきます。

001 void f()
002 {
003 volatile int vi = 1;
004 const volatile int cvi = 2;
005
006 const int& r1 = vi; // error. volatile な変数では初期化できない。
007 const int& r2 = cvi; // error. volatile は const であっても信用できない。
008 const volatile int& r3 = vi; // OK.
009 const volatile int& r4 = cvi; // OK.
010 }

volatile は「移り気な・気まぐれな」という意味のとおり、文脈とは関係のないところで変化するかも知れない変数を意味します。よく、最適化の対象にならない変数という解説を見かけますが、勝手に変化するかもしれないから結果的に最適化できないだけのことです。どちらにしろ volatile を使うことは稀かと思いますが、もし使う場合にはきちんと volatile をつけて参照するようにしましょう。
posted by シンビアン at 09:13| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

関数とリファレンス

関数には引数と戻り値が存在し、それぞれに変数, ポインタ, リファレンスを用いることができます。また、関数へのポインタと同様に、関数へのリファレンスというものも存在します。本章では、このような関数とリファレンスの関係について考えていこうと思います。

001 void f1(int i)
002 {
003 i += 10; // 単なるコピーに10を加える。
004 }
005
006 void f2(int* p)
007 {
008 *p += 10; // ポインタの参照先に10を加える。
009 }
010
011 void f() {
012 int i = 10;
013
014 f1(i);
015 cout << i << endl; // i の値は変化しない。(10を出力)
016 f2(&i);
017 cout << i << endl; // i の値は変化する。(20を出力)
018 }

しかし、C++ にはリファレンスによる「参照渡し」が存在します。仮引数にリファレンスを指定すれば、関数呼び出しによって引数を変更することができます。

001 void f3(int& i)
002 {
003 i += 10; // i は与えられた変数の別名。
004 }
005
006 void f() {
007 int i = 10;
008
009 f3(i);
010 cout << i << endl; // i の値は変化する。(20を出力)
011 }

つまり、C++ には関数呼び出しによって引数を変更する手段が2通り存在することになります。ポインタとリファレンスで述べたようにポインタには何も参照しない状態(ヌルポインタ)が存在するため、ポインタを用いて引数を変更する場合にはヌルポインタに気をつける必要があります。それでは、ポインタとリファレンスの使い分けは、関数呼び出しによって引数を変更する場合でも「何も参照しない場合があるかどうか」で区別すればよいのでしょうか。これに関しては「プログラミング言語 C++ 第3版」の「5.5 リファレンス」にて以下のように述べられています。

しかし、プログラムを読みやすくするために、引数を変更する関数は避けた方がよい場合が多い。代わりに、明示的に値を返すか、ポインタ引数を要求するのである。
- 中略 -
increment(x) という表記は、x=next(x)、incr(&x) とは異なり、xの値が変更されようとしていることを示す手掛かりを読者に与えない。そのため、“ただの”リファレンス引数は、リファレンス引数が変更されるというヒントをはっきりと与えるような名前が付けられた関数だけで使うようにすべきである。

先ほどの例で言えば、「f1(i) と同じような感覚で f3(i) を呼び出して i の値が変化してしまうと読みづらいプログラムになってしまう」といったところでしょうか。f2(&i) であれば、i の値が変更されるかも知れないという事を意識して以降の処理を読み進めることができます。

ところで、C言語の時代では、引数を変更する以外にもポインタ引数を用いる場合がありました。それは、コピーに時間がかかる場合です。先ほども述べたように「値渡し」では引数がまるごとコピーされてしまいます。これでは巨大な構造体を引数として渡したい場合に無駄なコピーが大量に発生してしまうため、C言語ではポインタを渡してこの問題に対処していました。このままでは引数を変更することもできますが、もし、引数を変更する必要がない場合には const なポインタを渡すようにするだけです。

そして、同じ事がリファレンスにも当てはまります。C++ では、無駄なコピーを抑えるためにリファレンスを用いることができ、値を変更したくない(させない)場合には const なリファレンスを使用します。この const なリファレンス引数は、C++ のプログラムにおいて最も頻繁に登場するリファレンスといえるでしょう。

引数の「参照渡し」には他にも以下のような利点があります。

* 不要なコンストラクタの呼び出しを抑える。
* スライシングを防止する。
* コピーコンストラクタが public でなくても使える。

詳しくは「Effective C++ 改訂2版」の「22項 値渡しよりも、リファレンス渡しを使おう」を参照してください。
posted by シンビアン at 09:02| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

リファレンスへのリファレンス

C++ では「リファレンスへのリファレンス」や「リファレンスへのポインタ」は禁止されています。これを読んで「え? 出来るんですけど…」と思った方、おそらく以下のような例を考えているのではないでしょうか。

001 void f() {
002 // リファレンスへのリファレンス?
003 int i; // int を用意
004 int& ri = i; // それを参照するリファレンス
005 int& ri2 = ri; // そのリファレンスを参照するリファレンス
006
007 // リファレンスへのポインタ?
008 int* pi = &ri; // リファレンスのポインタ
009 }

確かに「リファレンスへのリファレンス」や「リファレンスへのポインタ」と言えなくもないですが、これは単にリファレンスやポインタをリファレンスによって初期化しているだけです。C++ で禁止されている「リファレンスへのリファレンス」や「リファレンスへのポインタ」は以下のようなものです。

001 void f() {
002 // リファレンスへのリファレンス
003 int i; // int を用意
004 int& ri = i; // それを参照するリファレンス
005 int&& ri2 = ri; // おっと、&& は論理AND演算子だ!
006 int& & ri3 = ri; // リファレンスへのリファレンス(エラー!)
007
008 // リファレンスへのポインタ
009 int&* pi = &ri; // リファレンスへのポインタ(エラー!)
010 }

リファレンスは参照先の別名に過ぎず、実体はありません。実体のないリファレンスを指し示すことは出来ませんし、実体のないリファレンスのアドレスが存在しないことも納得できます。

しかし、テンプレートとリファレンスを併用すると、簡単に「リファレンスへのリファレンス」が生成されてしまいます。
これは bind1st や bind2nd と mem_fun, mem_fun_ref の組み合わせで発生する問題として有名です。Effective STL 50項でも取り上げられており「参照への参照問題」と訳されています。ここでは参照をリファレンスという用語で統一していますので、この問題は「リファレンスへのリファレンス問題」ということになるでしょう。

001 template struct A {
002 void f(const T& t) {} // T がリファレンスだとマズイ!
003 };
004
005 template void f1(void (*pf)(ArgType)) {
006 A a;
007 }
008 void f2(const int&) {}
009
010 void f() {
011 f1(f2); // ArgType が const int& ということは、T も const int& になってしまう。
012 }

標準化委員会はこの問題に対処するため、標準の変更を検討しているそうです。しかし、標準が改訂され、コンパイラベンダがそれに対応するまでにはもう少し時間がかかります。もし、今すぐにでもこの問題を解決したいならば Boost.org を参照しましょう。そこには、まさにこの問題を解決するための boost::call_traits というテンプレートが用意されています。
posted by シンビアン at 08:47| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

戻り値とリファレンス

戻り値とリファレンス

戻り値にリファレンスを用いる場合には、引数にリファレンスを用いる場合以上に注意が必要です。例えば、以下のようにうっかりローカル変数のリファレンスを返してしまうと、戻り値は関数呼び出しが終わった瞬間に不正な値となってしまいます。

001 struct test_obj
002 {
003 test_obj() { cout << "test_obj: construction" << endl; }
004 ~test_obj() { cout << "test_obj: destruction" << endl; }
005 int val_;
006 };
007
008 test_obj& f1()
009 {
010 test_obj obj;
011 obj.val_ = 1;
012 return obj; // おっと、ローカル変数を返してる。
013 }
014
015 void f()
016 {
017 test_obj& obj = f1(); // f1 の戻り値は関数の終わりで無効になっている。
018 cout << obj.val_ << endl; // もし動いても、それは偶然。
019 obj.val_ = 2; // これも危険。
020 cout << obj.val_ << endl; // やっぱり偶然。
021 }

メンバ変数を返すようにしていれば安心かというと、そうでもありません。例えば const なメンバ関数が const でないリファレンスメンバを返すと、以下のように奇妙な現象を引き起こします。(参照: Effective C++ 29項 内部データの「ハンドル」を返すのはやめよう)

001 class A {
002 int i_; // データ
003 int& r_; // そのリファレンス
004 public:
005 A() : i_(0), r_(i_) {}
006 int GetVal() const { return i_; } // データを返す
007 int& GetRef() const { return r_; } // あっ!
008 };
009
010 void f()
011 {
012 const A a; // a は const
013 cout << a.GetVal() << endl; // データは初期値
014 a.GetRef() = 1; // GetRef は const メンバ関数なので、この呼び出しはOK.
015 cout << a.GetVal() << endl; // const な a のデータが書き換わってしまう!
016 }

そもそも、リファレンスを返すインタフェースは実装に制限を加えてしまうという意見もあります。例えば、以下のように値を返すインタフェースであれば、その実装でメンバを返そうがテンポラリを返そうが問題ありません。

001 class Base {
002 public:
003 virtual ~Base() {}
004 virtual int GetVal() const = 0; // 値を返すインタフェースなら…
005 };
006
007 class A : public Base {
008 public:
009 virtual int GetVal() const { return 0; } // 定数でもOK.
010 };
011
012 class B : public Base {
013 public:
014 virtual int GetVal() const { return int(1); } // テンポラリもOK.
015 };
016
017 class C : public Base {
018 int i_;
019 public:
020 C() : i_(2) {}
021 virtual int GetVal() const { return i_; } // メンバ変数だってOK.
022 };

しかし、以下のようにリファレンスを返すインタフェースだったならば、その実装でテンポラリを返すことが出来なくなってしまいます。たとえ戻り値が const リファレンスであったとしても、「const なリファレンスの初期化によるテンポラリオブジェクトの延命」で述べたようにテンポラリオブジェクトの寿命が切れてしまうため、結果は悲惨なものとなってしまうでしょう。

001 class Base {
002 public:
003 virtual ~Base() {}
004 virtual const int& GetVal() const = 0; // リファレンスを返すインタフェースだと…
005 };
006
007 class A : public Base {
008 public:
009 virtual const int& GetVal() const { return 0; } // error.
010 };
011
012 class B : public Base {
013 public:
014 virtual const int& GetVal() const { return int(1); } // error.
015 };
016
017 class C : public Base {
018 int i_;
019 public:
020 C() : i_(2) {}
021 virtual const int& GetVal() const { return i_; } // メンバ変数ならOK.
022 };

確かにリファレンスを返せば効率は改善されるかも知れません。しかし、戻り値に限っていえば、不要なテンポラリオブジェクトを取り除くような最適化が規格によって許されています。この「戻り値最適化」に関しては「More Effective C++ 項目20: 戻り値最適化の促進」を参照していただくとして、まずは運命をコンパイラに委ね、値を返すような関数を基本としておきましょう。大抵の場合、効率が問題になって初めて戻り値をリファレンスにすべきかどうかで悩んでも遅くはないはずです。

これまで、効率の面だけに着目してきましたが、クラスの派生が絡んでくると状況は一変します。インタフェースを返す関数が値を返してしまうと「スライシング問題」を引き起こしてしまうため、そのような関数はポリモーフィズムが有効なリファレンスかポインタを返さなければなりません。

001 class Base {
002 public:
003 virtual ~Base() {}
004 virtual void Name() const { cout << "Base" << endl; }
005 };
006
007 class Sub : public Base { // Base から派生
008 public:
009 virtual void Name() const { cout << "Sub" << endl; }
010 };
011
012 Base GetSub1() { return Sub(); } // マズイ! Sub を返しているつもり。
013 const Base& GetSub2() { static Sub sub; return sub; } // これならOK.
014
015 void f() {
016 GetSub1().Name(); // Base と出力される。
017 GetSub2().Name(); // Sub と出力される。
018 }

つまり、関数の戻り値を「値渡し」にするか「参照渡し」にするかは、効率で決めるのではなく設計の一部ということです。チューニングの必要に迫られるまで、設計を歪めるほどの最適化は必要ありません
posted by シンビアン at 08:33| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

広告


この広告は60日以上更新がないブログに表示がされております。

以下のいずれかの方法で非表示にすることが可能です。

・記事の投稿、編集をおこなう
・マイブログの【設定】 > 【広告設定】 より、「60日間更新が無い場合」 の 「広告を表示しない」にチェックを入れて保存する。


×

この広告は1年以上新しい記事の投稿がないブログに表示されております。