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++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

Helloworldbasocを解析

ディレクトリ


ファイル


意味
aif helloworldbasicaif.rss リソース定義ファイル
  qgn_menu_demo_cxt.bmp アイコン用BitMAP 44x44
  qgn_menu_demo_cxt_mask.bmp アイコン用BitMAP(MASK)
  qgn_menu_demo_lst.bmp アイコン用BitMAP 42x29
  qgn_menu_demo_lst_mask.bmp アイコン用BitMAP(MASK)
data helloworldbasic.loc ローカル定義(文字列)
  helloworldbasic.rss menu用リソース
  helloworldbasic_caption.rss CAPTION用リソース
group ABLD.BAT bldmake filesで作成されたバッチファイル
  bld.inf bldmake用定義ファイル
  helloworldbasic.mmp bld用プロジェクトファイル
inc helloworldbasic.hrh コマンドコード
  helloworldbasic.pan panic codes
  helloworldbasicapplication.h CHelloWorldBasicApplicationクラス(CAknApplication)
  helloworldbasicappui.h CHelloWorldBasicAppUiクラス(CAknAppUi)
  helloworldbasicappview.h CHelloWorldBasicAppViewクラス(CCoeControl)
  helloworldbasicdocument.h CHelloWorldBasicDocumentクラス(CAknDocument)
sis helloworldbasic.pkg SIS作成用ファイル(ABLDで生成)
  helloworldbasic.SIS makesisで作成
src helloworldbasic.cpp 本体(Applicationクラスを呼び出す)
  helloworldbasicapplication.cpp アプリケーションクラス(ドキュメントの作成とUIの呼び出し)
  helloworldbasicappui.cpp UIクラス(Viewクラスの呼び出しとコマンドの処理)
  helloworldbasicappview.cpp ウインドウの生成と画面の線画
  helloworldbasicdocument.cpp Docuemntクラス、この場合はドキュメントの処理は無し、UIの生成のみ

これで、大体の構成はわかったような気がします
posted by シンビアン at 18:26| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

コード変換

TBool CBTMSG60AppView::CharConvertL(TInt aTargetCode, TDes & aDes) const
{
TBuf unicodeMessage;
TBuf8 jiscodeMessage;

unicodeMessage.Copy(aDes);

CCnvCharacterSetConverter* converter = CCnvCharacterSetConverter::NewLC();
if(converter->PrepareToConvertToOrFromL(aTargetCode,
iEikonEnv->FsSession()) != CCnvCharacterSetConverter::EAvailable)
User::Leave(KErrNotSupported);

if(CCnvCharacterSetConverter::EErrorIllFormedInput == converter->ConvertFromUnicode(jiscodeMessage, unicodeMessage))
User::Leave(KErrArgument); //Leave if error in conversion.

aDes.Copy( jiscodeMessage );

return ETrue;

}



呼び出すときは

iAppView->CharConvertL(KCharacterSetIdentifierShiftJis, textData);

って感じで。他の漢字コードを使いたいときは一つ目の引数を変える。charconv.h charconv.lib も忘れずに!!
posted by シンビアン at 18:20| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

エディタの入力モードを変更したい

RESOURCE DIALOG r_dataquery_data_query
{
flags=EGeneralQueryFlags;
buttons = R_AVKON_SOFTKEYS_OK_CANCEL;
items =
{
DLG_LINE
{
type = EAknCtQuery;
id = EDataQueryDlgCIdDataQuery;
control = AVKON_DATA_QUERY
{
layout = EDataLayout;
control =
EDWIN
{
avkon_flags = EAknEditorFlagLatinInputModesOnly;
width = KMaxCommandNameLength;
lines = 1;
maxlength = KMaxCommandNameLength;
};
};
}
};
}
posted by シンビアン at 18:18| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

プログラミング

# サンプルプログラムのビルド(エミュレータ)

全てのプログラミングの習得についていえることですが、「習うより慣れろ」が大きいと思います。幸い、SDKにはたっぷりのサンプルが付いていますのでこれを活用します。自分の希望に近いサンプルを見つけたら、コマンドプロンプトを起動して下記のコマンドを実行してみましょう。

* cd "C:\Symbian\8.0a\S60_2nd_FP2_J_B\Series60Ex\APP_NAME\group"
* bldmake bldfiles
* abld build wins udeb

ここまでで、エミュレータで動作するビルドが出来ました。エミュレータを起動して動作を確認してみましょう。

# IDE ターゲット用のビルド

* abld makefile vc6

"C:\Symbian\8.0a\S60_2nd_FP2_J_B\epoc32\BUILD\SYMBIAN\8.0A\S60_2ND_FP2_J_B \SERIES60EX\APP_NAME\"以下(設定により若干変わることあり)に VC6 の設定ファイル (*.dsw) が出来ます。これをダブルクリックして VC6 の IDE 環境でデバッグすることが出来ます。
posted by シンビアン at 17:20| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

UID

UID とは Symbian OS 上で動作するアプリケーションを特定するための ID です。各アプリケーションが一意の ID 保持しています。自作アプリの外部公開を目指す人はこの UID を取得する必要があります。uid AT symbiandevnet.com にメールで問い合わせます。
posted by シンビアン at 17:19| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

エミュと実機のビルド

エミュと実機でビルドの条件を少しだけ変えたい場合は以下のようにする。(NextTrain60 から引用)

#if __WINS__
_LIT(KTbls, "C:\\nexttrain60\\tbls\\");
#else
_LIT(KTbls, "E:\\nexttrain60\\tbls\\");
#endif

エミュレータのときは "_LIT(KTbls, "C:\\nexttrain60\\tbls\\");" がコンパイルされる。
posted by シンビアン at 17:15| Comment(1) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

UIDの変更箇所

本格的なアプリケーション開発を行うようになると UID が必要になってくる。アプリケーションを開発するときはサンプルコードを改造したり、Application Wizard を用いて開発することが多いと思う。どっちにしても UID を後から設定する必要がある。(ちなみに筆者は Application Wizard でうまく設定出来たためしがない…。)以下のファイルを編集する

* 〜Application.h
* 〜Aif.rss
* 〜.mmp
* 〜.pkg
posted by シンビアン at 17:12| Comment(1) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

日本語リソースの設定

Series60青本の230ページあたりに詳しい記述があるが、日本語(2バイト?)を取り扱うときは以下の点にも注意する必要がある。

* *.loc ファイル中に CHARACTER_SET UTF8 を記述する。*.loc ファイルそのものも UTF-8 形式にする
* 各言語のリソースを記述する *.l01 ファイルなども UTF-8 形式にする

ちなみに日本語の場合だと *.l32 になる。

*.pkg ファイルの記述を工夫すると各言語セットごとにファイル構成を変えることが出来る。これにより、各国に合わせたビットマップや音声などをセットアップすることが出来る。
posted by シンビアン at 17:11| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

ファイルの読み込み

# BOM の取り扱い

日本語を取り扱うときは UTF-8 形式のファイルを取り扱う必要がある。UTF-8 形式のファイルはファイルの先頭にBOMが付加されているのでファイルを読み込ませるときは予め、この部分を飛ばしてから読み込ませる必要がある。
# UTF-8 -> UNICODE

内部的には UNICODE で処理されているので UTF-8 を UNICODE に変換する必要がある。具体的には以下のAPIを使って変換する。

const TInt returnValue=CnvUtfConverter::
ConvertToUnicodeFromUtf8(unicodeOutputBuf, utf8Buf);
posted by シンビアン at 17:10| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

Designing C++ Applications for Series 60

用語集
このドキュメントでは、以下の用語および略語が使用されています。
用語 説明
Avkon Series 60 エクステンションおよび Uikon とその他 Symbian OS アプリケーションフレームワークへの変更
Uikon すべての Symbian OS デバイスに共通の UI およびコントロールのフレームワーク
posted by シンビアン at 17:05| Comment(1) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

Resource Struct RSS_SIGNATURE

RSS_SIGNATURE
Support

Supported from 6.0
Description

A utility struct that, used as the first resource in a resource file, provides a way to record what the resource offset is.

It contains two 32-bit fields: a version number and a self-referencing link. The RResourceFile::ConfirmSignatureL() function reads the signature resource and initialises the offset value from this resource.
See also:

* RResourceFile::ConfirmSignatureL()

LONG signature=EEikResourceSignatureValue;

A resource file version number.
SRLINK self;

A link to the current resource (i.e. itself). This should not be assigned a value.
posted by シンビアン at 17:01| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

Series 60 アプリケーションフレームワーク

オプションメニューの使用
4.1 オプションメニュー
Avkon オプションメニューは、 .rss ファイルに指定されているメニューバーとメニューペインから構築されます。メニューは、 EAknSoftkeyOptions ID によりバインドされている場合 WINS エミュレータの 'F1' キーまたは 'Options' ソフトキーの操作に応じて開きます。既存の 'Options' ソフトキーペインを使おうとするアプリケーションビューは、 R_AVKON_SOFTKEYS_OPTIONS_BACK CBA リソースを使用するべきです。
4.2 メニューセクションの定義
メニューペインセクションは MENU_PANE リソースストラクチャとして定義されます。メニューペインはそれぞれのメニューセクションに定義されるべきです。ひとつのセクションはそれぞれのメニューペイン領域:システム、アプリケーション、ビュー、コンテキストに使用されます。

メニューペインは次のように定義されます。

RESOURCE MENU_PANE r_system_menu
{
items =
{
MENU_ITEM { command = ECmdCut; txt = "Cut"; },
MENU_ITEM { command = ECmdCopy; txt = "Copy"; },
MENU_ITEM { command = ECmdPaste; txt = "Paste"; }
};
}

サブメニューペインは、サブメニューペインのリソース名を使って cascade パラメータにより指定できます。

例:

MENU_ITEM { command = ESystemOptions; txt =
"System Options"; cascade = r_system_options_menu; }

4.3 メニューセクションの組合せ
メニューセクションは MENU_BAR リソースにより結合されます。 これはメニューを構成するために結合されるすべてのセクションを表示します。メニューペインは、最後のセクションから先頭のセクションの順で定義されます。

メニューバーは次の様に定義されます。

RESOURCE MENU_BAR r_menuapp_menu
{
titles =
{
MENU_TITLE { txt = "System"; menu_pane =
r_system_menu; },
MENU_TITLE { txt = "App"; menu_pane =
r_app_menu; },
MENU_TITLE { txt = "View"; menu_pane =
r_view1_options_menu; },
MENU_TITLE { txt = "Context"; menu_pane =
r_context1_menu; }
};
}

オプションの txt パラメータは、明確にすることのみを目的としており、画面上ではまったく表示されません。但し、これらパラメータはリソースファイルには保存されます。

デフォルトメニューバーは EIK_APP_INFO リソースにより参照されます。これはアプリケーションの起動時に提供されるメニューバーです。

ビューアーキテクチャが使用される場合は、ビューのリソースストラクチャ内に各ビューのメニューリソースを設定するオプションがあります。
4.4 メニューセクションの変更
アプリケーションが使用するメニューバーリソースを変更することによりアプリケーション内でいつでもメニューセクションを変更することができます。

iEikonEnv->AppUiFactory()->MenuBar()->
SetMenuTitleResourceId(MENU_BAR_RESOURCE_ID);

これは、セクションの中のどれかひとつの内容を変更する必要があるたびに実行されるべきです。従って、考えられるメニューセクションの組合せの数だけ MENU_BAR リソースが定義されているべきです。つまり、ひとつのメニューバーは、ビューとコンテキストオプションの各組み合わせごとに定義されるべきだからです。

なお、ビューアーキテクチャが使用されていて、そのビュー自体のメニューシステムが現在使用されている場合、それは変更が必要なメニューバーの内容となります。

iMyView->MenuBar()->SetMenuTitleResourceId
(MENU_BAR_RESOURCE_ID);

4.5 メニューアイテムの変更
個々のメニューアイテムは、メニューが表示されるたびに変更される可能性があります。これにより、アプリケーションはアプリケーションの状態に応じてメニューアイテムを追加したり、削除したりできます。

アプリケーションUIは下記の仮想関数をオーバーライドするべきです。

void DynInitMenuPaneL
(TInt aResourceId, CEikMenuPane* aMenuPane);

これは、セクションのそれぞれがメニューに追加された後にコールされます。そして、追加されたセクションとビルドされているメニューペインのリソース ID でコールされます。

アプリケーションUIは下記の仮想関数もオーバーライドできます。

void DynInitMenuBarL
(TInt aResourceId, CEikMenuBar* aMenuBar);

これは、どんなセクションがメニューに追加される場合でもその前にコールされます。これはメニューに追加されるセクションを動的に変更するのに使用できます。つまり、コンテキストメニューセクションのリソース ID をあるアプリケーションの状態のために変更するために使用できます。
posted by シンビアン at 13:51| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

Series 60 アプリケーションフレームワーク

ベースクラス
3.2.1 CAknDocument

このクラスはアプリケーションドキュメントのベースクラスとして提供されます。このクラスを使うことにより、アプリケーションドキュメントファイルへのアクセスは開始されません。これは、大多数の Avkon アプリケーションでは許容されます。
3.2.2 CAknAppUi

すべての Avkon アプリケーション( Eiksrv とは別)はこのクラスから派生しなければなりません。

このクラスはいくつかの Avkon 固有の機能をサポートしています。これら機能については、 SDK 説明書で詳しく解説されています。

* KeySound サポート
* CBA ペインと ステータスペインのアクセサリー
* TextResolver - CAknAppUi::HandleError() から報告される Avkon 特有のエラー
* Avkon ビューアーキテクチャインテグレーション
* コントロールダンピング- デバッグ機能

3.2.3 CAknViewAppUi

すべてのビューアーキテクチャに基づくアプリケーションは、このクラスから派生しなければなりません。詳細は SDK 説明書に記載されています。
posted by シンビアン at 13:48| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

Series 60 アプリケーションフレームワーク

CEikApplication から派生する CAknApplication クラスが提供されます。このクラスは再実装により CEikApplication を特化させます。

* PreDocConstructL()
* OpenIniFileLC(RFs& aFs)

PreDocConstructL はまず構築中のアプリケーションのインスタンスが既に存在していないかどうかチェックするために実装されます。もしもそのインスタンスが既に存在する場合は、アプリケーションは存在するインスタンスに切り替え、その後終了します。このチェックは非埋め込みアプリケーションに対して行われます。

デフォルトでは、 Series 60 アプリケーションは、 .ini ファイルをサポートしていません。 OpenIniFileLC は、コールされるとただリーブする単純な実装によりオーバーライドされます。 .ini ファイルが使用される場合、アプリケーションはそのアプリケーションクラス内でこのメソッドを実行することを強制され、 CEikApplication::OpenIniFileLC をコールします。
posted by シンビアン at 13:45| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

ネイティブアプリケーションが携帯電話を変える! いよいよ日本でも本格デビュー

Symbian OSは、音声通話以外にさまざまなデータ処理機能を持つハイエンド携帯電話(=スマートフォン)用の標準OSとして幅広く採用されていますが、そこで使用されるアプリケーションは、一般のPCソフトのようにインストールして利用できるOS固有のアプリケーション(ネイティブアプリケーション)の時代を迎えて、大きく変化しようとしています。本書のテーマであるSymbian OS+ミドルウェア「Nokia Series 60」の環境は、その代表的な開発プラットフォームです。ヨーロッパをはじめとして海外ではすでにこの環境を実装したスマートフォンが市場を牽引していますが、日本でもノキア・ジャパンより端末の発売が発表されるなど、業界や開発者の注目を集めています。
posted by シンビアン at 10:39| Comment(0) | TrackBack(0) | Forum Nokia | このブログの読者になる | 更新情報をチェックする

ルネサス テクノロジ、Symbian のユーザー支援プログラムに参加

株式会社ルネサス テクノロジと英国 Symbian は2005年1月19日、ルネサステクノロジの super AND 型フラッシュメモリのドライバソフトウェアが、Symbian のユーザー支援プログラム「Companion Technology Program(CTP)」に参加し、Symbian OS を公式にサポートする、と発表した。

これによりルネサス テクノロジは、Symbian OS のライセンシーとして Symbian OS を搭載したスマートフォンを開発・製造する携帯電話メーカーに対し、同ドライバソフトウェアの検証時間を短縮し、動作確認できるシステムを提供する。

super AND 型フラッシュメモリは、データの読み出しを行う際、メモリチップ自身が変化した箇所を検出し訂正する「エラー訂正機能」や、書き換え回数を平準化する「ウェアレベリング機能」など、従来システム側で対応していたメモリ管理機能を内蔵し、システム設計負担を軽減させる。

CTP は、富士通、Motorola、Nokia、シャープ、Sony Ericsson など Symbian OS のライセンシーの技術革新を促進することを目指しており、Symbian OS ライセンシーに対して、事前に Symbian OS 環境での統合や動作確認された技術を提供することで、開発期間の短縮と、技術的なリスクを抑え製品の完成度を高めることができる。

また、Symbian OS ライセンシーは、CTP のソリューションを採用する際、各ソリューション・ベンダーと直接ライセンス交渉することも可能だ。
posted by シンビアン at 10:08| Comment(0) | TrackBack(0) | Forum Nokia | このブログの読者になる | 更新情報をチェックする

リファレンスの初期化

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

まず、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++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

シンボリックリファレンス

一般的なリファレンスの使い方では、式 $$var はリファレンス変数 $var のデリファレンスを表しています。それでは、$var がリファレンスではない場合はどうなるのでしょうか。$var の値が文字列の場合、たとえば "foo" であれば、$$var は $foo という変数を表します。これが「シンボリックリファレンス」です。次の例を見てください。

$foo = 10;
$var = "foo";
$$var = 100; # $foo を表すシンボリックリファレンス
print $foo; # 100 を表示

$$var を評価するとき、まず $var がハードリファレンスかどうか調べます。$var は "foo" という文字列なので、foo という名前の変数 $foo を評価しようとします。その結果、$foo に 100 が代入されます。この例では変数にアクセスしましたが、@$var とすれば配列 @foo に、%$var とすればハッシュ %foo にアクセスすることができます。

ただし、シンボリックリファレンスは大域変数しかアクセスできません。次の例を見てください。

sub bar {
my $foo = 10;
my $var = "foo";
print "$$var\n";
}

$foo = 100;
&bar; # 100 を表示する

関数 barの中で局所変数 $foo と $var を定義しています。$$var はシンボリックリファレンスで変数 $foo を表しますが、局所変数 $foo にアクセスするのではなく、大域変数の $foo にアクセスします。その結果 100 を表示します。
posted by シンビアン at 08:21| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

リファレンスの「ツボ」 〜 ポインタとはチト違う 〜

リファレンス(参照と呼ばれることもあります)は C++ で新たに追加された機能の1つです。これは、他のインスタンスを(文字通り)参照するという意味では、C の時代から備わっていたポインタと非常に良く似た機能といえるでしょう。その気になればリファレンスの代わりにポインタを使っても、さほど問題になることはありません。それでは、ポインタとリファレンスの違いは何なのでしょうか。また、リファレンスを使う上で注意すべきことは何なのでしょうか。今回は、このリファレンスについて深く掘り下げ、リファレンスの「ツボ」を発見していきたいと思います。
posted by シンビアン at 08:09| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

リファレンスの「ツボ」

* リファレンスとはポインタに若干の制限を加えた存在である。
* const リファレンスとテンポラリオブジェクトの寿命は、いつでも必ず等しいというわけではない。
* リファレンスを用いて引数を変更するのはやめよう。
* 戻り値にリファレンスを用いる場合は慎重に。
* テンプレートとリファレンスを併用する場合には「リファレンスへのリファレンス問題」に注意しよう。
posted by シンビアン at 08:08| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

ポインタとリファレンス

リファレンスは、変数の別名として作用するものでした。
これは、ポインタの仕組みによって構成されています。

リファレンスの正体は、「自動的に間接参照される定数ポインタ」です。

簡単に言い換えると

   ・常に*演算子が適用される

   ・ポインタ演算ができない

上のような制約があるポインタということになります。


*演算子が自動的に適用されるということは、常に間接参照されていること
になります。
よって、代入されたり参照されている先は常に変数ということになるので
別名を扱っているかのように見えます。


また、リファレンスは定数ポインタです。
ということは、ポインタ演算は全くできません。
つまり、配列の先頭要素を指して、そこから任意の要素にアクセスしたり
する事もできません。

あくまでリファレンスは、変数単体を示すものだということを忘れないで
ください。
そのため、ポインタのように高度なデータ構造を作り上げることは
できません。


リファレンスの価値はむしろ、記述が簡単になるということにあります。

例えば、よくある2数を交換するという関数の場合、

【ポインタ版】
   void exchange(int* a,int* b){
    int t;
    t = *a;
    *a = *b;
    *b = t;
   }

【リファレンス版】
   void exchange(int& a,int& b){
    int t;
    t = a;
    a = b;
    b = t;
   }

の様に、遥かにシンプルな記述ができます。
posted by シンビアン at 08:06| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

広告


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

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

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


×

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