2006年01月15日

仮想関数

基底クラスの関数を派生クラスで書き換える(横取りする、上書きする)ことをオーバーライドと言う。つまり基底クラスのある関数と同じ名前、同じ引数、同じ戻り値で、中身の違う関数を派生クラスで定義することを言う。仮想関数とは、オーバーライドをするためのメカニズムである。つまり基底クラスの動作または機能をあとから作成した派生クラスによってカスタマイズする手段を提供する。
メンバー関数の定義中に、戻り値の型の前に「virtual」というキーワードを付することにより、その関数は仮想関数となる。以下に簡単な例を示す。

仮想関数
// 親クラス
class Base{
public:
virtual void func(){
cout << "Base::func" << endl;
}
};

// 子クラス
class Deriv : public Base{
public:
virtual void func(){
cout << "Deriv::func" << endl;
}
};

void main(){
Base base;
Deriv deriv;

base.func();
deriv.func();
}

Base::func
Deriv::func

非仮想関数
// 親クラス
class Base{
public:
void func(){
cout << "Base::func" << endl;
}
};

// 子クラス
class Deriv : public Base{
public:
void func(){
cout << "Deriv::func" << endl;
}
};

void main(){
Base base;
Deriv deriv;

base.func();
deriv.func();
}

Base::func
Deriv::func

上記の例は簡単な例で、インスタンスを通じて関数を呼び出した場合である。インスタンスを通じて関数を呼び出す場合、「virtual」を付けても付けなくても動作は変わらない。

  仮想関数の効果が発揮されるのは、ポインタを通じて関数を呼び出す場合や、参照を通じて関数を呼び出す場合である。

仮想関数
// 親クラス
class Base{
public:
virtual void func(){
cout << "Base::func" << endl;
}
};

// 子クラス
class Deriv : public Base{
public:
virtual void func(){
cout << "Deriv::func" << endl;
}
};

void main(){
Base* pBase1 = new Base();
Base* pBase2 = new Deriv();
Deriv* pDeriv = new Deriv();

pBase1->func();
pDeriv->func();
pBase2->func();

delete pBase1;
delete pBase2;
delete pDeriv;
}

Base::func
Deriv::func
Deriv::func

非仮想関数
// 親クラス
class Base{
public:
void func(){
cout << "Base::func" << endl;
}
};

// 子クラス
class Deriv : public Base{
public:
void func(){
cout << "Deriv::func" << endl;
}
};

void main(){
Base* pBase1 = new Base();
Base* pBase2 = new Deriv();
Deriv* pDeriv = new Deriv();

pBase1->func();
pDeriv->func();
pBase2->func();

delete pBase1;
delete pBase2;
delete pDeriv;
}

Base::func
Deriv::func
Base::func

基底クラスであるBaseポインタ型に、派生クラスであるDerivをnewした場合(pBase2)を見てください。非仮想関数の場合は、Baseクラスのfunc関数が呼ばれています。これはポインタがBaseクラスのポインタだからです。(その中身がBaseクラスだろうが、Derivクラスだろうが関係なくポインタの型であるBaseのfunc関数が呼ばれます。)

  それに対して仮想関数では、ポインタが基底クラスであるBaseクラスののポインタであるにも関らず、その中身が派生クラスであるので、Derivクラスのfunc関数が呼び出されています。

  非仮想関数では、実態が派生クラスだろうが基底クラスだろうが、その入れ物を示すポインタや参照の型を見て、コンパイラがどちらのクラスの関数を呼び出すかを決めます。これを静的結合といいます。

  これに対して仮想関数では、コンパイラがどちらのクラスの関数を呼び出すのか決めるのではなく、実行時にポインタや参照が、その実態は派生クラスなのか基底クラスなのかを調べ、動的にどちらの関数を呼び出すかを決定します。これを動的結合といいます。なお、動的結合のメカニズムは後述します。

// 親クラス
class Base{
public:
virtual void func(){
cout << "Base::func" << endl;
}
virtual void func2(){
cout << "Base::func2" << endl;
}
};

// 子クラス
class Deriv : public Base{
public:
virtual void func(){
cout << "Deriv::func" << endl;
}
virtual void func2(){
Base::func2(); // 親クラスのfunc2を呼び出す
cout << "Base::func2" << endl;
}
};

void main(){
Base* pBase = new Deriv();

base->func();
base->Base::func();

cout << endl;
base->func2();

delete pBase;
}

Deriv::func
Base::func

Base::func2
Deriv::func2

また、派生クラスの仮想関数から親クラスの関数を呼び出すことも可能である。さらに、スコープ演算子「::」を利用することにより、明示的に基底クラスの関数を呼び出すことも可能である。(この場合は静的結合になる。)

  なお、基底クラスの関数が仮想関数の場合、その派生クラスでオーバーライドする関数に「virtual」を付けなくても、自動的にその関数も仮想関数となる(上記の例では Derivクラスのfunc関数)。しかし、一般的に省略せずに、派生クラスでも「virtual」を付ける場合が多い。
posted by シンビアン at 16:41| Comment(1) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

仮想デストラクタ

派生クラスを作成した場合、コンストラクタは、基底クラスのコンストラクタ、派生クラスのコンストラクタの順に呼ばれる。
そしてデストラクタは、派生クラスのデストラクタ、基底クラスのデストラクタの順に呼ばれる。
しかし、デストラクタが仮想でないと、基底クラスポインタに、派生クラスのインスタンスのポインタが代入されている場合、deleteしても、それは静的結合によってデストラクタが呼ばれるため、基底クラスのデストラクタしか呼ばれない
例では、デストラクタが非仮想の場合と仮想の場合を比較した。基底クラスのポインタに派生クラスをnewして、deleteしたときに違いが現れることを示している。

非仮想デストラクタ 仮想デストラクタ

// 親クラス
class Base{
public:
Base(){
cout << "Base::Base()" << endl;
}
~Base(){
cout << "Base::~Base()" << endl;
}
};

// 子クラス
class Deriv : public Base{
public:
Deriv(){
cout << "Deriv::Deriv()" << endl;
}
~Deriv(){
cout << "Deriv::~Deriv()" << endl;
}
};

void main(){
{
Deriv deriv;
}
cout << endl;

Base* pBase;
Deriv* pDeriv;

pBase = new Base();
delete pBase;

cout << endl;

pBase = new Deriv();
delete pBase;

cout << endl;

pDeriv = new Deriv();
delete pDeriv;
}

Base::Base()
Deriv::Deriv()
Derive::Deriv()
Base::Base()

Base::Base()
Base::Base()

Base::Base()
Deriv::Deriv()
Base::Base()

Base::Base()
Deriv::Deriv()
Derive::Deriv()
Base::Base()

仮想デストラクタ
// 親クラス
class Base{
public:
Base(){
cout << "Base::Base()" << endl;
}
virtual ~Base(){
cout << "Base::~Base()" << endl;
}
};

// 子クラス
class Deriv : public Base{
public:
Deriv(){
cout << "Deriv::Deriv()" << endl;
}
virtual ~Deriv(){
cout << "Deriv::~Deriv()" << endl;
}
};

void main(){
{
Deriv deriv;
}
cout << endl;

Base* pBase;
Deriv* pDeriv;

pBase = new Base();
delete pBase;

cout << endl;

pBase = new Deriv();
delete pBase;

cout << endl;

pDeriv = new Deriv();
delete pDeriv;
}

Base::Base()
Deriv::Deriv()
Deriv::~Deriv()
Base::~Base()

Base::Base()
Base::~Base()

Base::Base()
Deriv::Deriv()
Deriv::~Deriv()
Base::~Base()

Base::Base()
Deriv::Deriv()
Deriv::~Deriv()
Base::~Base()

このように派生クラスのインスタンスを作っているにもかかわらず、派生クラスのデストラクタが呼ばれないのは問題がある。したがって、今後、クラスが派生される可能性が少しでもあるばらば、 デストラクタは仮想とするべきである。
posted by シンビアン at 16:26| Comment(0) | TrackBack(1) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

サブタイプ

abstract宣言されたクラス、および、全てのインタフェースを便宜的に抽象型と
呼びます。また、abstractクラスをextendsするクラスや、インタフェースをimplementsする
クラスをサブタイプと呼ぶ。

abstract宣言されたメソッドは、それを宣言した抽象型のサブタイプで実際の処理内容を与え
ます。このとき、サブタイプをabstract宣言すれば、abstract宣言されているメソッドの一部
または全部の実装を与えずにおくことも可能で、その場合、改めてabstractメソッドを宣言す
る必要はありません。
posted by シンビアン at 16:08| Comment(0) | TrackBack(1) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

構造体

複数のデータをまとめて扱うには配列を用いましたが、配列では同じ型のデータしかまとめて扱う事はできません。

実際にプログラムを組んでいると、異なる型のデータをまとめて扱いたい場合がしばしばあります。たとえば、学生の成績を扱うときに、int型の学生番号と、char型配列の氏名と、double型の点数をまとめて扱えれば便利だと思いませんか?

「構造体」は幾つかの異なる型のデータをまとめて 1つのデータ型として扱うものなのです。
posted by シンビアン at 16:03| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

エミュレート

あるハードウェア向けに開発されたソフトウェアを、設計の異なる他のハードウェア上で実行させること。ハードウェアの設計の違いを吸収するため、元のハードウェアの "ふり" をする「エミュレータ」と呼ばれるソフトウェアを実行させ、その上でソフトウェアを動作させる。また、特定のハードウェアが行なう処理に似せた処理を別のハードウェアやソフトウェアで実行することをエミュレーションと呼ぶことがある。前者の意味の場合は、利用するデータ形式なども互換性があることが多いが、後者の場合はデータ的な互換性はないことがほとんどである。
posted by シンビアン at 16:00| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

RAM

Random Access Memory

 任意のアドレスを指定して読み書きすることが可能な半導体メモリ。電源を切ってしまうとデータが消えてしまう揮発性の記憶デバイスである(NVRAMという不揮発性のRAMも存在する)。 RAMを大雑把に分類すると、DRAMとSRAMの2種類がある。PC互換機では、メインメモリやキャッシュメモリのほか、ハードウェア構成などの情報を保持しているCMOS RAM、グラフィックスカード上のフレームバッファなど、いろいろなところでRAMが使用されている。  RAMのパッケージ表面には、型番の後ろに“-??”という形式でRAMのスピードを表わす数字が書き込まれていることが多い。一般的にこの数字は、メモリへのアクセスが始まってから実際にデータが読み書きされるまでの時間を表わしている(アクセスタイムと呼ばれる)。メインメモリ用のDRAMなら 60〜100nsec、外部キャッシュ用のSRAMなら8〜25nsec程度であることが多い。
posted by シンビアン at 15:54| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

リソース

資源。Macintoshにおいては、プログラムを構成するソフトウェア部品のこと。アプリケーションに共通のソフトウェア部品を使用することで、外観を統一し処理ルーチンを一元化することができる。リソースはもともと、初代のMacintoshには128Kというメモリしかなかったため、ソフトウェアのメモリ消費量の抑制とローカライズを簡便化する目的で考えられた。リソースの編集はApple社のResEditで行なえる。
posted by シンビアン at 15:52| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

メモリ

(1)コンピュータ本体の中にあり、情報を記憶しておく場所のこと。あらかじめ情報が書き込まれていて内容が変更できないROMと、内容を変更できるが電源を切ると内容が消えてしまうRAMがある。一般的にメモリといえば、メインメモリであるRAMのことを指す。「メモリが足りない」というときは、 RAMの容量が足りない状態のことをいう。

(2)メモリに関する設定をするコントロールパネル。ディスクキャッシュ、仮想メモリやRAMディスクなどの設定を行なう。
posted by シンビアン at 15:51| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

システムヒープ

システムが利用するメモリ空間。メモリ空間を管理するとき、アプリケーション用とシステム用のメモリ空間は区別される。システム用はシステムヒープと呼ばれ機能拡張ファイルなどはこちらに読み込まれる。
posted by シンビアン at 15:51| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

ヒープ

プログラムで利用可能なメモリ領域。OSは、この領域からメモリを取得してアプリケーションに提供したり、この領域に新しいプログラムをロードしたりする。
posted by シンビアン at 15:50| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

ヒープ

RAMのメモリ空間を管理する方式。メモリ空間をシステム用とアプリケーション用に分け、前者をシステムヒープ、後者をアプリケーションヒープと呼ぶ。プログラムからメモリの要求があると、どちらかのヒープでメモリ領域を確保する。ファイルからリソースを読み込む場合は、リソースの設定やプログラムの指定によってどちらのヒープに読み込むか決まる。アプリケーションヒープに確保したメモリブロックは、そのアプリケーションが終了すると同時に消滅するが、システムヒープに確保したメモリブロックは、明示的に消滅させるかシステムが終了するまで存在し続ける。したがって、システムのルーチンにパッチを当てる機能拡張などのプログラムはシステムヒープにコードを常駐させることがある。多数の機能拡張をインストールした状態では、システムヒープのサイズが不適当になったり極度に肥大化したりといった現象が起きてアプリケーションヒープを圧迫することがある。
posted by シンビアン at 15:49| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

ヒープ

OSやアプリケーションソフトが使用するメモリ領域の一種。用途に関係なく自由に確保することができる。

OSからはヒープメモリを確保した位置のアドレスが渡され、アプリケーションソフトはこの値を元にして確保したヒープ領域を使用する。ヒープメモリはアプリケーション側での自由度が高い反面、ガーベジコレクション機能がないプログラミング言語(の処理系)では、確保したヒープをすべて手作業で解放しなければならないため、誤解放や解放漏れなどのミスが起こりやすい。このヒープの解放漏れが「メモリリーク」である
posted by シンビアン at 15:45| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

C++での例外処理について

C++には例外機構が備わっているが、C言語との互換性を確保するためなのか、不可解な動作をするところが多い。

例外を送出しない関数宣言
関数(またはメソッド)宣言でthrow ()を付けるとその関数からは例外を投げないという意味になる。にもかかわらず、例外を投げることができる。
次のソースは、関数f()で例外を投げる。gcc 3.4.2 (Fedora Core 3) では、コンパイル時にエラーも警告も出ない。
3| #include
4| #include
5|
6| class E: public std::exception { };
7|
8| void f() throw() { throw E(); }
9|
10| int main() {
11| try {
12| f();
13| }
14| catch (E& e) {
15| printf("my exception thrown.\n");
16| }
17| catch (...) {
18| printf("something thrown.\n");
19| }
20| return 0;
21| }
これを実行すると、例外が捕捉されず、プログラムが終了 (abort) する。

C++では、関数宣言にthrow(型, ...)を付けないとどのような例外も投げることができる。そのため、ある関数の内部でそのような関数を呼び出していると、呼び出すほうの関数もあらゆる例外を送出する可能性がある。

そんなわけで関数宣言に列挙していない例外を投げるプログラムを書いてもエラーにしないようになっているのでは、と思う。しかし陽に投げる例外と例外宣言がある関数の呼び出しぐらいはコンパイル時にチェックしてほしい。

実際の動作だが、関数宣言にない例外を送出しようとすると、std::unexpected()が内部で呼び出され、デフォルトの動作はstd::terminate()を呼ぶようになっている。そのため、プログラムがいきなり終了することになる。

std::set_unexpected()とstd::set_terminate()でハンドラを変更することができる。次のようにすると、いったんハンドラが呼び出されるようになる。
1| #include
2| #include
3|
4| class E: public std::exception { };
5|
6| void f() throw(std::bad_exception) { throw E(); }
7|
8| void handler() {
9| printf("unexpected.\n");
10| throw std::bad_exception();
11| }
12|
13| int main() {
14| std::set_unexpected(handler);
15|
16| try {
17| f();
18| }
19| catch (E& e) {
20| printf("my exception thrown.\n");
21| }
22| catch (...) {
23| printf("something thrown.\n");
24| }
25| return 0;
26| }
しかし実際問題としてこのハンドラを書くのは難しい。

というのも、ハンドラの内部では一体どこでどのような例外が発生したために自分が呼び出されたのかを知るすべがない。さらに、ハンドラ内で何か例外を投げないと、元の例外が再び送出されてabortしてしまう。かといって元の関数で許可されていない例外を投げるとstd:: bad_exceptionを投げたものと見なされ、std::bad_exceptionも許可されていないとやっぱりabortしてしまう。

結局、何かログを取るくらいが関の山で、関数宣言でstd::bad_exceptionが許可されていることを期待してそれを投げるぐらいしかできない。元の関数に焦点を合わせると、列挙している例外以外が送出されることはない、ということになる。
posted by シンビアン at 15:41| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

変数ディスクリプタ

変数ディスクリプタ (variable descriptor)は,変数(またはポインタ変数によって指される変数)に関する情報(アドレスや所属するフィールド)を関数に受け渡すためのデータである
posted by シンビアン at 15:33| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

ファイルディスクリプタ

プログラムがアクセスするファイルや標準入出力などをOSが識別するために用いる識別子。0から順番に整数の値が割り当てられる。OSによってはファイルディスクリプタにバッファ管理機能なども含めた「ファイルハンドル」と呼ばれる管理体系が存在する。

ファイルディスクリプタには、識別子とともにファイル名、ファイルサイズ、プログラムが操作中のファイル内の位置、ファイル作成、更新日時などの情報が含まれており、OSは識別子によってどのファイルを操作するかを判断する。

通常、0:標準入力(stdin)、1:標準出力(stdout)、2:標準エラー出力(stderr)の3つはOS(シェル)が最初に用意するため、プログラムがファイルをオープンすると「3」から順番にディスクリプタが割り当てられる。
posted by シンビアン at 15:32| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

ディスクリプタ

【descriptor】オペレーティングシステムが管理するリソースに割り当てられる(通常は整数の)値。
posted by シンビアン at 15:30| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

依存関係

デザインパターン[ Observer ]

あるオブジェクトが状態を変えたときに、それに依存するすべてのオブジェクトに自動的にそのことが知らされ、また、それらが更新されるように、オブジェクト間に一対多の依存関係を定義する。
posted by シンビアン at 15:29| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

広告


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

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

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


×

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