CppUnit 導入ガイド
ダウンロードとインストール
CppUnitは http://sourceforoge/projects/cppunit/ からダウンロードできます。2002.11時点での最新版は1.8.0(βは1.9.10)です。
zipもしくはtar+gzipで圧縮されているので、適当なディレクトリに展開してください。
以下の説明はWindows/Visual C++ v6.0 環境で、CppUnitのルート・ディレクトリを<CPPUNIT>と表記します。
ライブラリのビルド
CppUnitはツール/ユーティリティの類ではありません。テスト対象およびテストコードと一緒にリンクして実行モジュールを生成する’ライブラリ’です。
CppUnitは様々なOS/処理系に対応しており、その使用に先だってライブラリをビルドしなければなりません。
Visual Studio IDE から プロジェクト:<CPPUNIT>\src\CppUnitLibraries.dsw をオープンし、ビルド/バッチビルド-ダイアログからcppunitおよびcppunit_dllをチェックしてビルドします。
これによって、<CPPUNIT>\libに必要なライブラリ(とDLL)が生成されます。DLLはパスの通ったディレクトリにコピーしておくとよいでしょう。DLL版CppUnitはテストの実行モジュール・サイズを小さく抑えることができます。

cppunit/cppunit_dll以外のライブラリはMFC環境下でテスト結果をグラフィカルに表示するときに利用します。詳しくはCppUnitディストリビューションに同梱されているドキュメントをご覧ください。
テストコードの雛型
テストコードの雛型を示します。この雛型は様々なテストに応じて書き換えて利用してください。
XXXTest.cpp
/*
* test skeleton XXXTest.cpp [1]
*/
// 必要なヘッダの #include [2]
// ex. #include "XXX.h"
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestAssert.h>
// XXX をテストする [3]
class XXXTest : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(XXXTest); // [4]
CPPUNIT_TEST(test_one); // [5]
CPPUNIT_TEST(test_two); // [5]
CPPUNIT_TEST(test_three); // [5]
CPPUNIT_TEST(test_four); // [5]
CPPUNIT_TEST(test_five); // [5]
CPPUNIT_TEST(test_six); // [5]
CPPUNIT_TEST_SUITE_END(); // [4]
private:
// 必要な メンバ変数/関数 [6]
public:
/*
* コンストラクタ/デストラクタ [7]
*/
XXXTest() {
}
~XXXTest() {
}
/*
* 初期化/後始末 [8]
*/
virtual void setUp() {
}
virtual void tearDown() {
}
/*
* テスト・ケース [9]
*/
void test_one() {
CPPUNIT_ASSERT( 0 == 1 );
}
void test_two() {
CPPUNIT_ASSERT_MESSAGE( "0 is not equal to 1", 0 == 1 );
}
void test_three() {
CPPUNIT_FAIL( "never reached here!" );
}
void test_four() {
CPPUNIT_ASSERT_EQUAL( 0, 1 );
}
void test_five() {
CPPUNIT_ASSERT_EQUAL_MESSAGE( "1 is not equal to 0", 0, 1 );
}
void test_six() {
CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.23, 1.24, 0.001 );
}
};
/*
* register test suite
*/
CPPUNIT_TEST_SUITE_REGISTRATION(XXXTest); // [10]
- [1] ファイル名
- 慣習的に、テスト対象であるモジュール名の末尾に’Test’を付加します。たとえば、’Counter’のテストコードは’CounterTest.cpp’のように。
- [2] インクルード
- 必要なインクルードをここに記述します。
- [3] テストクラス
-
テストクラスはテスト対象に対するテスト・ケースの集合体で、
ファイル名と同じクラス名とするのが望ましいでしょう。 - [4] テスト・スイート
-
CPPUNIT_TEST_SUITE(テストクラス名);とCPPUNIT_TEST_SUITE_END();の間にテスト・ケースを記述します。 - [5] テスト・ケース(メソッド)の登録
-
CPPUNIT_TEST(テスト・ケース名);によって、テスト・ケースを登録します。 - [6] メンバ変数/関数
- 各テスト・ケースの実行に必要なメンバ変数/関数を宣言します。
- [7] コンストラクタ/デストラクタ
- 必要に応じ、コンストラクタ/デストラクタを定義します。なお、テストクラスのインスタンスは各テスト・ケース毎に生成されます。
- [8] 初期化/後始末
-
setUp(初期化)とtearDown(後始末)は、それぞれ各テスト・ケース実行の直前/直後に呼び出されます。 - [9] テスト・ケース(メソッド)
- テスト・ケースは引数/戻り値を持たないメソッドで、テスト項目毎に用意します。
- [10] テスト・スイート登録
-
CPPUNIT_TEST_SUITE_REGISTRATION(テストクラス名);によって、テスト・スイートが登録されます。
アサート・マクロ
各テスト・ケースの中では、テスト対象が正常に機能しているかを検証するコードを記述します。
以下のアサート・マクロが用意されています。
各アサート・マクロはそれぞれ特定の条件を満たさないとき、テスト失敗(failre)とみなしてそれ以降の処理をせずにテスト・ケースから脱出し、次のテスト・ケースに移ります。
CPPUNIT_ASSERT( condition );- conditionが偽(false,0)であったとき、失敗します。
CPPUNIT_ASSERT_MESSAGE( message, condition );- conditionが偽であったとき、失敗します。このときmessageを出力します。
CPPUNIT_FAIL( message );- 必ず失敗します。messageを出力します。
CPPUNIT_ASSERT_EQUAL( expected, actual );- 得られた結果actualが期待する値expectedでなかったとき、すなわちexpected != actualのときに失敗します。
CPPUNIT_ASSERT_EQUAL_MESSAGE( message, expected, actual );- 得られた結果actualが期待する値expectedでなかったとき、すなわちexpected != actualのときに失敗します。このときmessageを出力します。
CPPUNIT_ASSERT_DOUBLES_EQUAL( expected, actual, delta );- 得られた結果actualと期待する値expectedとの差がdeltaより大きいとき、失敗します。
メイン・ルーチン
以上のようにして作られたいくつかのテスト・スイートを実行し、その結果を出力するメイン・ルーチンの雛形を示します。
出力フォーマットに応じ、3種の雛形を用意しました。それぞれの出力結果をコードと併せて示します。
テキスト形式
#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/TextOutputter.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
int main() {
CppUnit::TextUi::TestRunner runner;
runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest());
CppUnit::Outputter* outputter =
new CppUnit::TextOutputter(&runner.result(),std::cout);
runner.setOutputter(outputter);
return runner.run() ? 0 : 1;
}
!!!FAILURES!!! Test Results: Run: 6 Failures: 6 Errors: 0 1) test: XXXTest::test_one (F) line: 49 XXXTest.cpp assertion failed - Expression: 0 == 1 2) test: XXXTest::test_two (F) line: 53 XXXTest.cpp assertion failed - 0 is not equal to 1 3) test: XXXTest::test_three (F) line: 57 XXXTest.cpp forced failure - never reached here! 4) test: XXXTest::test_four (F) line: 61 XXXTest.cpp equality assertion failed - Expected: 0 - Actual : 1 5) test: XXXTest::test_five (F) line: 65 XXXTest.cpp equality assertion failed - Expected: 0 - Actual : 1 - 1 is not equal to 0 6) test: XXXTest::test_six (F) line: 69 XXXTest.cpp equality assertion failed - Expected: 1.23 - Actual : 1.24
コンパイラ出力形式
#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/CompilerOutputter.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
int main() {
CppUnit::TextUi::TestRunner runner;
runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest());
CppUnit::Outputter* outputter =
new CppUnit::CompilerOutputter(&runner.result(),std::cout);
runner.setOutputter(outputter);
return runner.run() ? 0 : 1;
}
XXXTest.cpp(44):Assertion Test name: XXXTest::test_one assertion failed - Expression: 0 == 1 XXXTest.cpp(48):Assertion Test name: XXXTest::test_two assertion failed - 0 is not equal to 1 XXXTest.cpp(52):Assertion Test name: XXXTest::test_three forced failure - never reached here! XXXTest.cpp(56):Assertion Test name: XXXTest::test_four equality assertion failed - Expected: 0 - Actual : 1 XXXTest.cpp(60):Assertion Test name: XXXTest::test_five equality assertion failed - Expected: 0 - Actual : 1 - 1 is not equal to 0 XXXTest.cpp(64):Assertion Test name: XXXTest::test_six equality assertion failed - Expected: 1.23 - Actual : 1.24 Failures !!! Run: 6 Failure total: 6 Failures: 6 Errors: 0
XML形式
#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/XmlOutputter.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
int main() {
CppUnit::TextUi::TestRunner runner;
runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest());
CppUnit::Outputter* outputter =
new CppUnit::XmlOutputter(&runner.result(),std::cout);
runner.setOutputter(outputter);
return runner.run() ? 0 : 1;
}
<?xml version="1.0" encoding='ISO-8859-1' standalone='yes' ?>
<TestRun>
<FailedTests>
<FailedTest id="1">
<Name>XXXTest::test_one</Name>
<FailureType>Assertion</FailureType>
<Location>
<File>XXXTest.cpp</File>
<Line>49</Line>
</Location>
<Message>assertion failed
- Expression: 0 == 1
</Message>
</FailedTest>
<FailedTest id="2">
<Name>XXXTest::test_two</Name>
<FailureType>Assertion</FailureType>
<Location>
<File>XXXTest.cpp</File>
<Line>53</Line>
</Location>
<Message>assertion failed
- 0 is not equal to 1
</Message>
</FailedTest>
<FailedTest id="3">
<Name>XXXTest::test_three</Name>
<FailureType>Assertion</FailureType>
<Location>
<File>XXXTest.cpp</File>
<Line>57</Line>
</Location>
<Message>forced failure
- never reached here!
</Message>
</FailedTest>
<FailedTest id="4">
<Name>XXXTest::test_four</Name>
<FailureType>Assertion</FailureType>
<Location>
<File>XXXTest.cpp</File>
<Line>61</Line>
</Location>
<Message>equality assertion failed
- Expected: 0
- Actual : 1
</Message>
</FailedTest>
<FailedTest id="5">
<Name>XXXTest::test_five</Name>
<FailureType>Assertion</FailureType>
<Location>
<File>XXXTest.cpp</File>
<Line>65</Line>
</Location>
<Message>equality assertion failed
- Expected: 0
- Actual : 1
- 1 is not equal to 0
</Message>
</FailedTest>
<FailedTest id="6">
<Name>XXXTest::test_six</Name>
<FailureType>Assertion</FailureType>
<Location>
<File>XXXTest.cpp</File>
<Line>69</Line>
</Location>
<Message>equality assertion failed
- Expected: 1.23
- Actual : 1.24
</Message>
</FailedTest>
</FailedTests>
<SuccessfulTests></SuccessfulTests>
<Statistics>
<Tests>6</Tests>
<FailuresTotal>6</FailuresTotal>
<Errors>0</Errors>
<Failures>6</Failures>
</Statistics>
</TestRun>
時空を越えるオブジェクト part-2

RDBとオブジェクト指向
前のアーティクル "時空を越えるオブジェクト"では、主記憶上にあるオブジェクトをファイルに落とし、そしてそれを復元するからくり "シリアライズ"について解説しました。
シリアライズによって保存されたオブジェクトはアプリケーションとは切り離されます。主記憶上に完全に復元されない限り、利用することはできません。シリアライズ(Serialize:直列化)ってくらいだから、シーケンシャルアクセスなわけで、主記憶と同じようにファイルのあちこちに飛び回って必要なデータを参照/更新できるわけではありません。
シリアライズはとても有用な機構です。が、シリアライズでは解決できない場面はいくらでもあります。早い話が主記憶に納まらないほど大量のデータを処理しなければならない場合です。
それほど大量のデータを扱わなければならないとき、僕たちがよく使うのはデータベース、特にRDB(Relational Data Base)ですわね。 RDBはひとつのデータを表現するフィールドの集合をずらーっと並べた表を複数個用意し、それらをキー項目で関係(Relation)づけて巨大なひとつの表であるかのように見せてくれます。そしてその(仮想的な)表に対して追加/削除/変更および検索といった操作をやらせてくれます。
で、このRDBとオブジェクト指向、残念ながら相性がいいとは思えないんです。RDBに格納されているひとつひとつのレコードは、それを構成する各フィールドの型とサイズそしてフィールド数はデータベース設計時に固定されます。たとえば電話帳を作ることを考えたとき、レコードは、
- 名前・文字列・32桁
- 電話番号・文字列・11桁
なんてなフィールドが定義されることでしょう。
僕はC++屋ですから、
class Person {
char* name_;
char* phone_;
public:
const char* getName() const;
void setName(const char*);
const char* getPhone() const;
void setPhone(const char*);
...
};
のようなクラスを用意し、データベース上のレコードをPersonのインスタンスとして扱いたいのです。
すると当然の事ながら、Personのインスタンスとデータベース上のレコードとのマッピングすなわち、
new Person–> データベースへレコードを追加delete Person–> データベースからレコードを削除Person::getName()–> データベース上のレコードを参照Person::setName()–> データベース上のレコードを更新
して欲しくなり、Personに対する各メソッドの呼び出しに応じたデータベースの操作を行なわねばなりません。
さらに僕はこんなこともやりたくなります。
class Programmer : public Person {
public:
const char* getLanguage() const;
void setLanguage(const char*);
...
};
Personの属性である"名前"と"電話番号"に"得意な言語"を加えたProgrammerを定義し、電話帳の中にPersonとProgrammerとを一緒に登録したいのです。RDBではこんな要求を解決してくれるのかしら…
ObjectStore PSE
ここに紹介するPSE(Persistent Storage Engine)はObject Design社のオブジェクト指向データベース"ObjectStore"からオブジェクトの永続メカニズムを取り出したライブラリで、Visual C++版をオブジェクト・デザイン・ジャパンからダウンロードできます [1]。
RDB屋さんにはPSEがデータベースとはとても思えないでしょう。
PSEはアプリケーションに"不揮発性メモリ"を使わせてくれるライブラリだと考えてもらってかまいません。つまり、オブジェクトを"不揮発性メモリ"上に置くことにより、アプリケーションを終了しようがマシンの電源を落とそうが、何度でも復元できるのです(もちろん僕らのマシンにはそんな不揮発性メモリなんか積んではいませんから、ファイルに保存されたメモリ・イメージを"仮想的"不揮発性メモリとして使わせてくれるのですが)。
ビルトイン型の永続化
ではまず簡単な例から。
#include <iostream>
using namespase std;
int main() {
int array = new int[10];
int i;
// write
for ( i = 0; i < 10; ++i ) {
array[i] = i * i;
}
// read
for ( i = 0; i < 10; ++i ) {
cout << array[i] << ' ';
}
cout << endl;
delete[] array;
return 0;
}
このコードに現れるint配列arrayをPSEで永続化しましょう。
#include <os_pse/ostore.hh>
#include <iostream>
#include <cstdio>
using namespace std;
int main() {
OS_PSE_ESTABLISH_FAULT_HANDLER
objectstore::initialize();
remove("array.db");
os_database* db = os_database::create("array.db"); // 1
int* array = new (db, os_ts<int>::get(), 10) int[10]; // 2
for ( int i = 0; i < 10; ++i ) { // 3
array[i] = i * i;
cout << array[i] << ' ';
}
cout << endl;
os_database_root* db_root = db->create_root("array"); // 4
db_root->set_value(array); // 5
db->save(); // 6
cout << "wrote." << endl;
db->close(); // 7
OS_PSE_END_FAULT_HANDLER
return 0;
}
- データベース
"array.db"を生成します。 -
int配列をデータベース上に作成します。
newに続く()内の引数にはそれぞれ、- データベース
- 型情報
- 要素数(1のときは省略可)
を与えます。この例では10個の
intをデータベース上に作成しています。 arrayの各要素に値をセットします。arrayをデータベースから読み込むための手掛かりとなるdb_rootを作ります。db_rootは文字列で識別され、ひとつのデータベースにいくつものdb_rootを作ることができます。db_rootにarrayをセットします。- データベースを
save()することで、arrayの内容がデータベースに固定されます。 - データベースをクローズします。
これで書き込みは完了です。作成されたデータベース "array.db"を別のアプリケーションで読み出しましょう。
#include <os_pse/ostore.hh>
#include <iostream>
using namespace std;
int main() {
OS_PSE_ESTABLISH_FAULT_HANDLER
objectstore::initialize();
os_database* db = os_database::open("array.db"); // 1
os_database_root* db_root = db->find_root("array"); // 2
int* array = static_cast<int*>(db_root->get_value());// 3
for ( int i = 0; i < 10; ++i ) { // 4
cout << array[i] << ' ';
}
cout << endl;
db->close(); // 5
OS_PSE_END_FAULT_HANDLER
return 0;
}
- データベースをオープンします。
- 書き込み時に作成した
db_rootを見つけ、 - 格納された
int配列をたぐり寄せます。 arrayの内容を出力します。- データベースをクローズします。
書き込み/読み込み時、arrayの各要素にアクセスしている部分に注目してください。通常の配列要素のアクセスとまったく同じです。データベースにアクセスしているとは思えないでしょう?
PSEのスゴいところは"迫真の言語透過性"なんですね。
クラスの永続化
では次にユーザ定義型(クラス)の永続化です。
/* --- point.h --- */
#ifndef __POINT_H__
#define __POINT_H__
#include <iostream>
class Point {
int x_;
int y_;
public:
explicit Point(int x =0, int y =0) : x_(x), y_(y) {}
friend std::ostream& operator<<(std::ostream& s, const Point p) {
return s << '(' << p.x_ << ',' << p.y_ << ')';
}
};
#endif
このクラスPointを永続化しましょう。
/* --- write.cpp --- */
#include <os_pse/ostore.hh>
#include <iostream>
#include <cstdio>
#include "point.h"
using namespace std;
int main() {
OS_PSE_ESTABLISH_FAULT_HANDLER
objectstore::initialize();
remove("point.db");
os_database* db = os_database::create("point.db");
Point* array = new (db, os_ts<Point>::get(), 10) Point[10];
for ( int i = 0; i < 10; ++i ) {
array[i] = Point(i,-i);
cout << array[i] << ' ';
}
cout << endl;
os_database_root* db_root = db->create_root("point");
db_root->set_value(array);
db->save();
cout << "wrote." << endl;
db->close();
OS_PSE_END_FAULT_HANDLER
return 0;
}
/* --- read.cpp --- */
#include <os_pse/ostore.hh>
#include <iostream>
#include "point.h"
using namespace std;
int main() {
OS_PSE_ESTABLISH_FAULT_HANDLER
objectstore::initialize();
os_database* db = os_database::open("point.db");
os_database_root* db_root = db->find_root("point");
Point* array = static_cast<Point*>(db_root->get_value());
for ( int i = 0; i < 10; ++i ) {
cout << array[i] << ' ';
}
cout << endl;
db->close();
OS_PSE_END_FAULT_HANDLER
return 0;
}
クラスを永続化するときは、 PSEに対してあらかじめ永続化するクラスの型情報を教えておかなければなりません。
型情報を登録するには、スキーマ(schema:概要/輪郭)を記述します。
/* --- schema.scm --- */ #include <os_pse/ostore.hh> #include "point.h" OS_MARK_SCHEMA_TYPE(Point)
というスキーマ定義ファイルを作成し、PSEに付属するユーティリティpssgに食わせます。
pssg -asof schems.obj schema.scm
すると、永続オブジェクトの型情報schema.objが生成されます。これを一緒にリンクしてアプリケーションを作ります。スキーマ定義ファイルには、永続化したいクラスの数だけ
OS_MARK_SCHEMA_TYPE(クラス名)
を列挙します。
ポインタの永続化
冒頭に示したクラスPersonの永続化を試みましょう。
class Person {
char* name_;
char* phone_;
public:
...
};
クラスのメンバ変数にポインタを含むとき、その内容もデータベース内に確保しなければなりません。
/* --- person.h --- */
#ifndef __PERSON_H__
#define __PERSON_H__
#include <iostream>
class Person {
char* name_;
char* phone_;
char* dup(const char* str);
public:
explicit Person(const char* name ="", const char* phone ="");
Person& operator=(const Person&);
‾Person();
friend std::ostream& operator<<(std::ostream& s, const Person& p) {
return s << '(' << p.name_ << ',' << p.phone_ << ')';
}
};
#endif
/* --- person.cpp --- */
#include <os_pse/ostore.hh>
#include <cstring>
#include "person.h"
Person::Person(const char* name, const char* phone) {
name_ = dup(name);
phone_ = dup(phone);
}
Person& Person::operator=(const Person& p) {
delete[] name_;
name_ = dup(p.name_);
delete[] phone_;
phone_ = dup(p.phone_);
return *this;
}
Person::‾Person() {
delete[] name_;
delete[] phone_;
}
char* Person::dup(const char* str) {
int l = strlen(str) + 1;
char* p = new(os_database::of(this), os_ts<char>::get(), l) char[l];
strcpy(p,str);
return p;
}
メソッドdup()内にあるnew()の第一引数os_database::of(this)は、thisがデータベース上に確保されているならそのデータベースを、そうでないならヒープを表すtransientデータベースを返します。
したがって、
Person* person = new(db, os_ts<Person>::get()) Person("police","110");
ならばメンバname_,phone_はdb上に、
Person* person = new Person("police","110");
ならばヒープ上に確保されます。うまくできているものです。
さて、それではここまでのまとめとして、

なる関係を持つPerson*[],Link<Person>,Stack<Person>を永続化しましょう。ソースは少々長くなるので書き込み/読み込みのmainのみを示します。全ソースは
。
/* --- write.cpp --- */
#include <os_pse/ostore.hh>
#include <iostream>
#include <cstdio>
#include "person.h"
#include "programmer.h"
#include "grade.h"
#include "stack.h"
using namespace std;
// os_ts<X*> を作る...
OS_MARK_SCHEMA_PTR_TYPE(Person*)
#define db_new(type,size) new(db,os_ts< type >::get(),size)
int main() {
OS_PSE_ESTABLISH_FAULT_HANDLER
objectstore::initialize();
remove("person.db");
os_database* db = os_database::create("person.db");
os_database_root* db_root;
db_root = db->create_root("stack");
Stack<Person>* stack = db_new(Stack<Person>,1) Stack<Person>;
db_root->set_value(stack);
Link<Person>* link = 0;
Person** array = db_new(Person*,3) Person*[3];
db_root = db->create_root("array");
db_root->set_value(array);
Person* person;
person = db_new(Person,1) Person("Ann",25);
person->print();
array[0] = person;
stack->push(person);
link = db_new(Link<Person>,1) Link<Person>(person,link);
person = db_new(Programmer,1) Programmer("Bob",30,"BASIC");
person->print();
array[1] = person;
stack->push(person);
link = db_new(Link<Person>,1) Link<Person>(person,link);
person = db_new(GradedProgrammer,1) GradedProgrammer("Charlie",30,"C++",3);
person->print();
array[2] = person;
stack->push(person);
link = db_new(Link<Person>,1) Link<Person>(person,link);
db_root = db->create_root("link");
db_root->set_value(link);
cout << endl;
db->save();
cout << "wrote." << endl;
db->close();
OS_PSE_END_FAULT_HANDLER
return 0;
}
/* --- read.cpp ---*/
#include <os_pse/ostore.hh>
#include <iostream>
#include "person.h"
#include "programmer.h"
#include "grade.h"
#include "stack.h"
using namespace std;
int main() {
OS_PSE_ESTABLISH_FAULT_HANDLER
objectstore::initialize();
os_database* db = os_database::open("person.db");
os_database_root* db_root;
Person* person;
Stack<Person>* stack;
cout << "array: ";
db_root = db->find_root("array");
Person** array = static_cast<Person**>(db_root->get_value());
for ( int i = 0; i < 3; ++i ) {
array[i]->print();
}
cout << endl;
cout << "stack: ";
db_root = db->find_root("stack");
stack = static_cast<Stack<Person>*>(db_root->get_value());
while ( !stack->empty() ) {
person = stack->top();
person->print();
stack->pop();
}
cout << endl;
cout << "link: ";
db_root = db->find_root("link");
Link<Person>* link = static_cast<Link<Person>*>(db_root->get_value());
while ( link ) {
link->data->print();
link = link->next;
}
cout << endl;
db->close();
OS_PSE_END_FAULT_HANDLER
return 0;
}
PSEのダウンロードファイルには、ライブラリ、ヘッダのほか、ドキュメント群および各種サンプルが詰まっています。
RDBがしっくりいかないアナタ、ぜひPSEで遊んでみてください。
CORBAやCOMと組み合わせれば、分散オブジェクトデータベースによるちょいとしたMulti-tierアプリケーションが作れますよ!
編集部注
- ^ 2010年2月現在、ObjectStore は 日本プログレス株式会社の製品となっているようです。
[Java入門]3.10 まとめ
これで、Java言語でプログラミングをする際に必要となる基礎知識は一通り紹介しました。この章の内容については、後半部分、特にサブクラスを使うあたりから、少々難しいと感じられた方も多いでしょう。しかし、すべてが理解できなかったと心配することはありません。他の章を読み、また、時には本章に戻るなどして、ゆっくりと理解を深めていけばいいのです。
もちろん、本章では十分に説明していない部分もありますし、実際にプログラミングをするにあたって知っておいた方がいいノウハウなどにもほとんど触れていません。しかし、そういったことは、本書の第7章の言語仕様を参照したり、その他の章を読みながらプログラミングを実践するなかで段々と理解し、身についていくものなので、心配しないでください。
次章はいよいよお待ちかね、アプレットプログラミングです。
[Java入門]3.9 車とガソリンタンクの共通点
車に乗っていてガス欠になってしまったことはありませんか? バイクならスタンドまで手で押していってもいいのですが、さすがに車を押していくのはちょっと無理です。
こんなとき、何かガソリンを入れられるような缶などがあれば、それだけをもってガソリンスタンドを探しにいけますね。とりあえずそのスタンドにたどり着けるくらいのガソリンを買ってくればいいのですから。
そうすると、車だけでなく、缶やポリタンク等もガソリンスタンドを利用できるということになります。前節で定義したガソリンタンクのクラスを思い出してみてください。そもそもこのタンククラスは、車クラスからガソリンを入れるという機能を取り出したものですから、当然のことながらガソリンを入れることができます。
同じ様に、缶クラスやポリタンククラスを定義してみると、すべてガソリンを入れるという同じ機能を持っていることになるでしょう。
ここで、実際にガソリンを入れるガソリンスタンドの立場になってみると、形態は違ってもみんな同じお客さんですから、同じ様な手順で扱いたいですね。車クラスとタンククラスではこの手順は共通しており、ガソリンタンクの残量を確認するときにはisNotFullメソッドを使い、給油するときにはrefuelメソッドを使っています。
これは、車の持つ機能の一部を取り出してタンククラスを作ったのですから、当然のことかもしれません。しかし、新たに缶クラスやポリタンククラスを作るときにも同じ機能を同じメソッドで同じように扱えるようにするべきでしょう。そうしなければ、ガソリンスタンドは、違う容器が来る度に、給油の方法を変えなければならなくなります。同じ「ガソリンを入れられるもの」なのですから、同じメソッドでガソリンを入れることができた方が何かと便利です。
このためには、ガソリンを入れるために必要なメソッドを、ひとかたまりの決まった枠組みとして定義しておけばよいでしょう、そして、「ガソリンを入れられるもの」のクラスは必ずその枠組みに従ってメソッドを定義するようにすればよいのです。
3.9.1 ガソリンを入れることができるインターフェース
Javaには、まさにこれを実現するための仕組みが用意されています。
それは、インターフェースというものです。つまり、インターフェースはある機能に必要なメソッドの枠組で、なおかつその機能を実現するのに十分なメソッドから構成されています。
それでは実際に、「ガソリンを入れられる」という機能のインターフェースを定義してみます。まず、名前を付けましょう。ガソリンを入れられるのですから、Refuelableにします。このインターフェースに必要なメソッドは、タンククラスが持っているメソッドそのものです。インターフェースではメソッド名だけ決めていればいいので、変数については考えなくてかまいません。まとめると以下のようになります。
- Java: Refuelable.java
-
interface Refuelable { boolean isNotFull(); void refuel(int amount); }
このソースは、$chapter3/ex8/Refuelable.javaにあります。
メソッドの記述が、クラス定義のときとはちょっと違っています。メソッドの中身がなく、セミコロン(;) があるだけです。
これには理由があります。車クラスとタンククラスのクラス定義を思い出してください。車クラスのisNotFullメソッドと、タンククラスのisNotFullメソッドは、名前は同じでしたが、変数の構成や実際のメソッド中の手続きは異なっていました。ですから、実際の手続きを示す部分までインターフェースで記述することはできないのです。
インターフェースはメソッドの名前を決めるだけで、メソッドの具体的な動作には関知しません。つまりインターフェースという機構は、同じ機能を別なクラスで実現するための枠組みと考えることができるわけです。
■
- C++: Refuelable.h
-
#ifndef REFUELABLE_H__ #define REFUELABLE_H__ class Refuelable { public: virtual ~Refuelable(); virtual bool isNotFull() const =0; virtual void refuel(int amount) =0; }; #endif - C++: Refuelable.cpp
-
#include "Refuelable.h" Refuelable::~Refuelable() { }
- C#: Refuelable.cs
-
interface Refuelable { bool isNotFull(); void refuel(int amount); }
- VB: Refuelable.vb
-
Interface Refuelable Function isNotFull() As Boolean Sub refuel(ByVal amount As Integer) End Interface
□
3.9.2 Refuelable インターフェースの実装
インターフェースに対して、各クラスにおけるメソッドの具体的な動作のことを実装と言います。そして、あるクラスがあるインターフェースで決められた機能をすべて持っているとき、そのクラスはそのインターフェースを実装していると言います。
それでは、インターフェースを実装するクラスは、どのように記述すればよいのでしょうか。ガソリンスタンドの例で見てみましょう。CarクラスもTankクラスもメソッド自体は既に定義されているので、クラスの定義の一箇所だけ変更すればよいでしょう。クラス名の後ろにimplementsに続けて実装するインターフェース名を記述するだけです。この場合であれば、それぞれ次のように記述します。
class Car implements Refuelable {
: (省略)
}
class Tank implements Refuelable {
: (省略)
}
なお、それぞれの完全なソースは$chapter3/ex8/Car.javaと$chapter3/ex8/Tank.javaにあります。前の版と実際に比べて、本当にこれしか変更していないということを確認してください。
この場合はどちらのクラスもスーパークラスを決めていないのでextendsはありませんが、ある場合にはimplementsはスーパークラス名の後ろに書きます。
■ C++:
class Car : virtual public Refuelable {
: (省略)
};
class Tank : virtual public Refuelable {
: (省略)
};
□
■ C#:
class Car : Refuelable {
: (省略)
}
class Tank : Refuelable {
: (省略)
}
□
■ VB:
Class Car
Implements Refuelable
: (省略)
Public Function isNotFull() As Boolean Implements Refuelable.isNotFull
Return tank_.isNotFull()
End Function
Public Sub refuel(ByVal amount As Integer) Implements Refuelable.refuel
tank_.refuel(amount)
End Sub
: (省略)
End Class
Class Tank
Implements Refuelable
: (省略)
Public Function isNotFull() As Boolean Implements Refuelable.isNotFull
Return remain_ < capacity_
End Function
Public Sub refuel(ByVal amount As Integer) Implements Refuelable.refuel
remain_ = remain_ + amount
End Sub
End Class
□
3.9.3 GasStation クラスへの修正
これで車クラスもタンククラスもRefuelableインターフェースとして扱えるようになりましたので、GasStationの方もRefuelableインターフェースを使うようにしましょう。
refuelメソッドでCarクラスを使っていた部分を、すべてRefuelableインターフェースを使うように書き換えます。
public int refuel(Refuelable aRefuelable) {
int charged = 0;
while ( aRefuelable.isNotFull() ) {
aRefuelable.refuel(1);
charged = charged + 1;
}
return charged;
}
■ C++
int GasStation::refuel(Refuelable* aRefuelable) {
int charged = 0;
while ( aRefuelable->isNotFull() ) {
aRefuelable->refuel(1);
charged = charged + 1;
}
return charged;
}
□
■ C#
public int refuel(Refuelable aRefuelable) {
int charged = 0;
while ( aRefuelable.isNotFull() ) {
aRefuelable.refuel(1);
charged = charged + 1;
}
return charged;
}
□
■ VB
Public Function refuel(ByRef aRefuelable As Refuelable) As Integer
Dim charged As Integer = 0
While aRefuelable.isNotFull()
aRefuelable.refuel(1)
charged = charged + 1
End While
Return charged
End Function
□
3.9.4 GasStationSimulation クラスへの修正
GasStationSimulationクラスはどうでしょうか。Carクラスを使っていたところを全部Refuelableに直すというわけにはいきません。インターフェースは実装を規定していないため、変数や他のメソッドの構成などがわからないので、オブジェクトを作ることはできないのです。
それでは困るので、お客さんごとに乱数を使って車かタンクのどちらかを自動的に選ぶようにしましょう。現実ならば車の方が圧倒的に多いのでしょうけど、ここでは簡単にほぼ半々だということにしてしまいましょう。乱数の値が負か、0以上かで分けます。タンクについては常に空だということにします。
以上のことを考慮すると、GasStationSimulationクラスは次のようになります。
- Java: GasStationSimulation.java
-
import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.Random; class GasStationSimulation { private GasStation aGS_; private int askInt(String message) { try { System.out.print(message); System.out.flush(); String line = new BufferedReader(new InputStreamReader(System.in)).readLine(); return Integer.parseInt(line); } catch ( java.io.IOException e ) { System.out.println("could not get input. Sorry."); return 0; } } private void dealCustomer() { Random aRandom = new Random(); int charged[] = new int[5]; boolean isCar[] = new boolean[5]; int totalAmount = 0; Refuelable aRefuelable; for ( int i = 0; aGS_.isNotEmpty() && i < charged.length; i = i + 1 ) { if ( aRandom.nextInt() < 0 ) { int initial = aRandom.nextInt() % Car.tankCapacity(); if ( initial < 0 ) { initial = -initial; } aRefuelable = new Car(initial); isCar[i] = true; } else { int capa = aRandom.nextInt() % Car.tankCapacity(); if ( capa < 0 ) { capa = -capa; } aRefuelable = new Tank(capa, 0); isCar[i] = false; } charged[i] = aGS_.refuel(aRefuelable); totalAmount = totalAmount + charged[i]; } System.out.print("No."); for ( int i = 0; i < charged.length; i++ ) { if ( isCar[i] ) { System.out.print("\t" + i + "(Car)"); } else { System.out.print("\t" + i + "(Tank)"); } } System.out.print("\namount:"); for ( int i = 0; i < charged.length; i++ ) { System.out.print("\t" + charged[i]); } System.out.print("\nprice:"); for ( int i = 0; i < charged.length; i++ ) { int price = aGS_.price(charged[i]); aGS_.addSales(price); System.out.print("\t" + price); } System.out.println("\ntotal: " + totalAmount + " [liter] / " + aGS_.totalSales() + " [yen]"); } public void simulate() { System.out.println("For all gas stations:"); int initial = askInt(" initial amount? "); int unitPrice = askInt(" unit price? "); GasStation.setUnitPrice(unitPrice); loop: while ( true ) { System.out.println("\nSelect command:"); int type = askInt(" 1) Normal 2) Discount other) exit "); switch ( type ) { case 1: aGS_ = new GasStation(initial); break; case 2: int border = askInt("borderline amount? "); aGS_ = new DiscountGasStation(initial, border); break; default: break loop; } dealCustomer(); } } public static void main(String[] args) { GasStationSimulation simulation = new GasStationSimulation(); simulation.simulate(); } }
■
- C++: GasStationSimulation.h
-
#ifndef GASSTATIONSIMULATION_H__ #define GASSTATIONSIMULATION_H__ #include <string> #include "GasStation.h" class GasStationSimulation { private: GasStation* aGS_; int askInt(const std::string& message); void dealCustomer(); public: void simulate(); }; #endif - C++: GasStationSimulation.cpp
-
#include <iostream> #include <cstdlib> // rand #include <vector> // vector #include "GasStationSimulation.h" #include "Car.h" #include "Tank.h" #include "GasStation.h" #include "DiscountGasStation.h" int GasStationSimulation::askInt(const std::string& message) { std::cout << message << std::flush; int result; if ( std::cin >> result ) { return result; } std::cout << "could not get input. sorry." << std::endl; return 0; } void GasStationSimulation::dealCustomer() { std::vector<int> charged(5); typedef std::vector<int>::size_type size_type; bool isCar[5]; int totalAmount = 0; Refuelable* aRefuelable; for ( size_type i = 0; aGS_->isNotEmpty() && i < charged.size(); ++i ) { if ( std::rand() % 2 == 0 ) { int initial = std::rand() % Car::tankCapacity(); aRefuelable = new Car(initial); isCar[i] = true; } else { int capa = std::rand() % Car::tankCapacity(); aRefuelable = new Tank(capa, 0); isCar[i] = false; } charged[i] = aGS_->refuel(aRefuelable); totalAmount = totalAmount + charged[i]; delete aRefuelable; } std::cout << "No."; for ( size_type i = 0; i < charged.size(); ++i ) { if ( isCar[i] ) { std::cout << '\t' << static_cast<unsigned>(i) << "(Car)"; } else { std::cout << '\t' << static_cast<unsigned>(i) << "(Tank)"; } } std::cout << "\namount:"; for ( size_type i = 0; i < charged.size(); ++i ) { std::cout << '\t' << charged[i]; } std::cout << "\nprice:"; for ( size_type i = 0; i < charged.size(); ++i ) { int price = aGS_->price(charged[i]); aGS_->addSales(price); std::cout << '\t' << price; } std::cout << "\ntotal: " << totalAmount << " [liter] / " << aGS_->totalSales() << " [yen]" << std::endl; } void GasStationSimulation::simulate() { std::cout << "For all gas stations:" << std::endl; int initial = askInt(" initial amount? "); int unitPrice = askInt(" unit price? "); GasStation::setUnitPrice(unitPrice); bool keepGoing = true; while ( keepGoing ) { std::cout << "\nSelect command:" << std::endl; int type = askInt(" 1) Normal 2) Discount other) exit "); switch ( type ) { case 1: aGS_ = new GasStation(initial); break; case 2: { int border = askInt("borderline amount? "); aGS_ = new DiscountGasStation(initial, border); } break; default: keepGoing = false; } if ( keepGoing ) { dealCustomer(); delete aGS_; aGS_ = 0; } } }
- C#: GasStationSimulation.cs
-
class GasStationSimulation { private GasStation aGS_; private int askInt(string message) { System.Console.Write(message); System.Console.Out.Flush(); try { return System.Int32.Parse(System.Console.ReadLine()); } catch ( System.Exception ) { System.Console.WriteLine("could not get input. sorry."); return 0; } } private void dealCustomer() { System.Random aRandom = new System.Random(); int[] charged = new int[5]; bool[] isCar = new bool[5]; int totalAmount = 0; Refuelable aRefuelable; for ( int i = 0; aGS_.isNotEmpty() && i < charged.Length; ++i ) { if ( aRandom.Next(2) == 0 ) { int initial = aRandom.Next(Car.tankCapacity); aRefuelable = new Car(initial); isCar[i] = true; } else { int capa = aRandom.Next(Car.tankCapacity); aRefuelable = new Tank(capa, 0); isCar[i] = false; } charged[i] = aGS_.refuel(aRefuelable); totalAmount = totalAmount + charged[i]; } System.Console.Write("No."); for ( int i = 0; i < charged.Length; ++i ) { if ( isCar[i] ) { System.Console.Write("\t" + i + "(Car)"); } else { System.Console.Write("\t" + i + "(Tank)"); } } System.Console.Write("\namount:"); for ( int i = 0; i < charged.Length; ++i ) { System.Console.Write("\t" + charged[i]); } System.Console.Write("\nprice:"); for ( int i = 0; i < charged.Length; ++i ) { int price = aGS_.price(charged[i]); aGS_.addSales(price); System.Console.Write("\t" + price); } System.Console.WriteLine("\ntotal: " + totalAmount + " [liter] / " + aGS_.totalSales + " [yen]"); } public void simulate() { System.Console.WriteLine("For all gas stations:"); int initial = askInt(" initial amount? "); GasStation.unitPrice = askInt(" unit price? "); bool keepGoing = true; while ( keepGoing ) { System.Console.WriteLine("\nSelect command:"); int type = askInt(" 1) Normal 2) Discount other) exit "); switch ( type ) { case 1: aGS_ = new GasStation(initial); break; case 2: int border = askInt("borderline amount? "); aGS_ = new DiscountGasStation(initial, border); break; default: keepGoing = false; break; } if ( keepGoing ) { dealCustomer(); } } } [System.STAThread] static void Main(string[] args) { GasStationSimulation simulation = new GasStationSimulation(); simulation.simulate(); } }
- VB: GasStationSimulation.vb
-
Class GasStationSimulation Private aGS_ As GasStation Private Function askInt(ByVal message As String) System.Console.Write(message) System.Console.Out.Flush() Try Return System.Int32.Parse(System.Console.ReadLine()) Catch e As System.Exception System.Console.WriteLine("could not get input. Sorry.") Return 0 End Try End Function Private Sub dealCustomer() Dim aRandom As System.Random = New System.Random() Dim charged(5) As Integer Dim isCar(5) As Boolean Dim totalAmount As Integer = 0 Dim aRefuelable As Refuelable Dim i As Integer For i = 0 To charged.Length - 1 If aRandom.Next(2) = 0 Then Dim initial As Integer = aRandom.Next(Car.tankCapacity) aRefuelable = New Car(initial) isCar(i) = True Else Dim capa As Integer = aRandom.Next(Car.tankCapacity) aRefuelable = New Tank(capa, 0) isCar(i) = False End If charged(i) = aGS_.refuel(aRefuelable) totalAmount = totalAmount + charged(i) Next System.Console.Write("No.") For i = 0 To charged.Length - 1 If isCar(i) Then System.Console.Write(vbTab + CStr(i) + "(Car)") Else System.Console.Write(vbTab + CStr(i) + "(Tank)") End If Next System.Console.Write(vbCrLf + "amount:") For i = 0 To charged.Length - 1 System.Console.Write(vbTab + CStr(charged(i))) Next System.Console.Write(vbCrLf + "price:") For i = 0 To charged.Length - 1 Dim price As Integer = aGS_.price(charged(i)) aGS_.addSales(price) System.Console.Write(vbTab + CStr(price)) Next System.Console.WriteLine(vbCrLf + "total: " + CStr(totalAmount) + " [liter] /" _ + CStr(aGS_.totalSales()) + " [yen]") End Sub Public Sub simulate() System.Console.WriteLine("For all gas stations:") Dim initial As Integer = askInt(" initial amount? ") GasStation.unitPrice = askInt(" unit price? ") Dim keepGoing As Boolean = True While keepGoing System.Console.WriteLine(vbCrLf + "Select command:") Dim type As Integer = askInt(" 1) Normal 2) discount other) exit ") Select Case type Case 1 aGS_ = New GasStation(initial) Case 2 Dim border As Integer = askInt("borderline amount? ") aGS_ = New DiscountGasStation(initial, border) Case Else keepGoing = False End Select If keepGoing Then dealCustomer() End If End While End Sub Shared Sub Main() Dim simulation As GasStationSimulation = New GasStationSimulation() simulation.simulate() End Sub End Class
□
3.9.5 実行結果
Refuelableインターフェースを使うガソリンスタンドシミュレーションの実行結果は、次のようになります。
For all gas stations:
initial amount? 250
unit price? 108
Select command:
1) Normal 2) Discount other)exit 1
No. 0(Car) 1(Car) 2(Tank) 3(Car) 4(Tank)
amount: 43 5 21 33 30
price: 4644 540 2268 3564 3240
total: 132 [liter] / 14256 [yen]
Select command:
1) Normal 2) Discount other)exit 2
borderline amount? 25
No. 0(Car) 1(Car) 2(Car) 3(Car) 4(Tank)
amount: 7 46 5 49 6
price: 756 4738 540 5047 648
total: 113 [liter] / 11729 [yen]
Select command:
1) Normal 2) Discount other)exit 3
[Java入門]3.8 ガソリンタンクを独立させる
これまで、ガソリンは車に入れるものと単純に考えてきました。しかし実際の車にはガソリンタンクが合って、ガソリンはそのガソリンタンクに入れておくのが普通です。
ガソリンタンクは車の内部にあって、そこにガソリンを入れるための給油口を用意したり、ガソリンの残量を示すメーターを取り付けているわけです。ガソリンがどのくらい残っているのかとか、後何リットル入るかなどの情報は、実は車自体の情報ではなく、ガソリンタンクの情報なのです。
しかし、ガソリンを入れるときには、普通「車に入れる」と考えます。実際、ガソリンスタンドに行って、「この車の内部にあるガソリンタンクを満タンにしてください」という人はまずいないでしょう。
こうして考えてみると、ガソリンタンクと言うのもまたクラスとしてとらえることができるでしょう。用量が決まっていて、今どのくらいはいっているのかを情報として持っており、満タンかどうかを調べることができて、もちろんガソリンを入れることもできる。変数やメソッドが思い浮かんでは来ませんか?
さらに、ここまでの例で車の情報や機能として考えていたものは、実は車のガソリンタンクが持っている情報や機能だったとも言えるかもしれません。
3.8.1 Tank クラス
それではここで、ガソリンタンクのクラスを定義してみましょう。今まで考えてきた車そのものと思っていいわけですから、以前の車クラスを参考にソースを書いてみましょう。変数やメソッドは同様のはずです。
クラスの名前はTankとします。なお、このソースは $chapter3/ex7/Tank.javaにあります。
- Java: Tank.java
-
class Tank { private int capacity_; private int remain_; public Tank(int tankCapacity, int initialRemain) { capacity_ = tankCapacity; remain_ = initialRemain; } public boolean isNotFull() { return remain_ < capacity_; } public void refuel(int amount) { remain_ = remain_ + amount; } }
変数が2つとコンストラクタがひとつ、そしてメソッドが2つ用意されています。タンクといっても、ものによって容量が異なるので、変数capacity_からstaticの指定を外してインスタンスごとに保持するようにしています。そして、コンストラクタでタンク全体の容量とガソリンの残量を与えるようにしました。
■
- C++: Tank.h
-
#ifndef TANK_H__ #define TANK_H__ class Tank { private: int capacity_; int remain_; public: Tank(int tankCapacity, int initialRemain); bool isNotFull() const; void refuel(int amount); }; #endif - C++: Tank.cpp
-
#include "Tank.h" Tank::Tank(int tankCapacity, int initialRemain) { capacity_ = tankCapacity; remain_ = initialRemain; } bool Tank::isNotFull() const { return remain_ < capacity_; } void Tank::refuel(int amount) { remain_ = remain_ + amount; }
- C#: Tank.cs
-
class Tank { private int capacity_; private int remain_; public Tank(int tankCapacity, int initialRemain) { capacity_ = tankCapacity; remain_ = initialRemain; } public bool isNotFull() { return remain_ < capacity_; } public void refuel(int amount) { remain_ = remain_ + amount; } }
- VB: Tank.vb
-
Class Tank Private capacity_ As Integer Private remain_ As Integer Public Sub New(ByVal tankCapacity As Integer, ByVal initialRemain As Integer) capacity_ = tankCapacity remain_ = initialRemain End Sub Public Function isNotFull() As Boolean Return remain_ < capacity_ End Function Public Sub refuel(ByVal amount As Integer) remain_ = remain_ + amount End Sub End Class
□
3.8.2 Car クラスへの修正
タンクのクラスができたので、車クラスがこれを使うように変更しましょう。
Carクラスはガソリンの残量を直接持つかわりに、Tankクラスのインスタンスを保持するように変更します。変数の名前は、わかりやすくtank_ということにしましょう。ガソリンタンクの残量はタンク自身が保持するようになったので、Carクラスには変数remain_は必要ありません。定義を省いてしまいましょう。
変数tankCapacity_はどうでしょうか。車はあいかわらずタンクの容量が一定ということにして、これの定義は残すことにしましょうか。
もちろんメソッドとコンストラクタも変更する必要があります。それぞれを変数tank_を使うように書き換えると、Carクラスのソースは次のようになります。このソースは$chapter3/ex7/Car.javaにあります。
- Java: Car.java
-
class Car { private static final int tankCapacity_ = 50; private Tank tank_; public Car(int initialRemain) { tank_ = new Tank(tankCapacity_, initialRemain); } public boolean isNotFull() { return tank_.isNotFull(); } public void refuel(int amount) { tank_.refuel(amount); } public static int tankCapacity() { return tankCapacity_; } }
コンストラクタでは、タンクの容量とガソリン残量を与えてTankクラスのインスタンスを生成し、変数tank_に保持しています。2つのメソッドでは、自分で残量と容量を比較したり、残量に給油分を足したりしないで、tank_のメソッドを呼び出していることに注目してください。このようにしてオブジェクトの内部で、別なオブジェクトの機能を使うことができるのです。
■
- C++: Car.h
-
#ifndef CAR_H__ #define CAR_H__ #include "Tank.h" class Car { private: static const int tankCapacity_; Tank tank_; public: explicit Car(int initialRemain); bool isNotFull() const; void refuel(int amount); static int tankCapacity(); }; #endif - C++: Car.cpp
-
#include "Car.h" const int Car::tankCapacity_ = 50; Car::Car(int initialRemain) : tank_(tankCapacity_,initialRemain) { } bool Car::isNotFull() const { return tank_.isNotFull(); } void Car::refuel(int amount) { tank_.refuel(amount); } int Car::tankCapacity() { return tankCapacity_; }
- C#: Car.cs
-
class Car { private static int tankCapacity_ = 50; private int remain_; public Car(int initialRemain) { remain_ = initialRemain; } public bool isNotFull() { return remain_ < tankCapacity_; } public void refuel(int amount) { remain_ = remain_ + amount; } public static int tankCapacity { get { return tankCapacity_; } } }
- VB: Car.vb
-
Class Car Private Shared tankCapacity_ As Integer = 50 Private tank_ As Tank Public Sub New(ByVal initialRemain As Integer) tank_ = New Tank(tankCapacity_, initialRemain) End Sub Public Function isNotFull() As Boolean Return tank_.isNotFull() End Function Public Sub refuel(ByVal amount As Integer) tank_.refuel(amount) End Sub Public Shared ReadOnly Property tankCapacity() As Integer Get Return tankCapacity_ End Get End Property End Class
□
3.8.3 実行結果
車クラスの使い方自体は変わっていませんので、その他のクラスについては特に変更する必要はありません。したがって、実行結果も毎回の給油量が変化するだけで、基本的には同じです。
For all gas stations:
initial amount? 250
unit price? 108
Select command:
1) Normal 2) Discount other)exit 1
Car no. 0 1 2 3 4
amount: 14 29 4 22 32
price: 1512 3132 432 2376 3456
total: 101 [liter] / 10908 [yen]
Select command:
1) Normal 2) Discount other)exit 2
borderline amount? 25
Car no. 0 1 2 3 4
amount: 28 2 31 30 6
price: 2884 216 3193 3090 648
total: 97 [liter] / 10031 [yen]
Select command:
1) Normal 2) Discount other)exit 3
ところで賢明な読者の方々は、ガソリンスタンドにも実はタンクがあるということに気づかれていることでしょう。まさしくその通りです。ガソリンスタンドの定義も、タンククラスを利用するように書き換えることができます。これは読者への課題ということにしますので、是非チャレンジしてみてください。
STL Samples : <stack>
スタック
#include <iostream> #include <stack> using namespace std; void std_stack() { cout << "stack" << endl; stack<int> stk; cout << "push... "; for ( int i = 0; i < 5; i++ ) { stk.push(i); cout << i << ' '; } cout << endl << "pop... "; while ( !stk.empty() ) { cout << stk.top() << ' '; stk.pop(); } cout << endl; }
STL Samples : <list>
双方向リスト
#include <iostream> #include <list> #include "to_string.h" #include "foo.h" using namespace std; void std_list() { cout << "list" << endl; list<Foo> lf; for ( int i = 1; i < 6; i++ ) lf.push_back(i%3); cout << to_string(lf.begin(), lf.end()) << endl; cout << "reverse..." << endl; lf.reverse(); cout << to_string(lf.begin(), lf.end()) << endl; cout << "sort..." << endl; lf.sort(); cout << to_string(lf.begin(), lf.end()) << endl; cout << "unique..." << endl; lf.unique(); cout << to_string(lf.begin(), lf.end()) << endl; }
STL Samples : <vector>
可変長配列
vector
#include <iostream> #include <vector> #include "to_string.h" #include "foo.h" using namespace std; void std_vector() { cout << "vector" << endl; vector<Foo> vf(5); for ( int i = 0; i < 5; i++ ) vf[i] = i; cout << to_string(vf.begin(), vf.end()) << endl; while ( !vf.empty() ) { cout << "remove last element: " << vf.back(); vf.pop_back(); cout << ": " << to_string(vf.begin(), vf.end()) << endl; } }
STL Samples : <map>
重複を許さない辞書
map
重複を許す辞書
multimap
#include <iostream>
#include <string>
#include <map>
using namespace std;
template<class Pair>
void print_pair(const Pair& x) {
cout << '(' << x.first << ':' << x.second << ") ";
}
void std_map() {
cout << "map" << endl;
const char* number[] = { "zero", "one", "two", "three" };
map<int,string> m;
int i;
for ( i = 0; i < 4; ++i )
m[i] = number[i];
map<int,string>::iterator it;
for ( it = m.begin(); it != m.end(); ++it)
print_pair(*it);
cout << endl;
for ( i = 2; i < 6; ++i ) {
it = m.find(i);
if ( it != m.end() )
cout << "key:" << it->first << " --> value:" << it->second << endl;
else
cout << i << " not found." << endl;
}
}
void std_multimap() {
cout << "multimap" << endl;
const char* jp[] = { "いち", "に", "さん", "よん" };
const char* uk[] = { "one ", "two", "three" };
const char* gr[] = { "eins", "zwei" };
multimap<int,string> m;
int i;
for ( i = 0; i < 4; ++i ) m.insert(make_pair(i+1,jp[i]));
for ( i = 0; i < 3; ++i ) m.insert(make_pair(i+1,uk[i]));
for ( i = 0; i < 2; ++i ) m.insert(make_pair(i+1,gr[i]));
typedef multimap<int,string>::iterator iter;
iter it;
for ( it = m.begin(); it != m.end(); ++it)
print_pair(*it);
cout << endl;
pair<iter,iter> range;
for ( i = 0; i < 6; ++i ) {
range = m.equal_range(i);
if ( range.first != range.second ) {
cout << "key:" << i << " --> value:";
while ( range.first != range.second ) {
cout << range.first->second << ' ';
++range.first;
}
cout << endl;
} else {
cout << i << " not found." << endl;
}
}
}
STL Samples : <set>
重複を許さない集合
set
重複を許す集合
multiset
#include <iostream> #include <string> #include <set> #include "to_string.h" using namespace std; void std_set() { cout << "set" << endl; set<int> s; int i; for ( i = 0; i < 4; ++i ) s.insert(i); cout << to_string(s.begin(),s.end()) << endl; for ( i = 2; i < 5; ++i ) { set<int>::iterator it = s.find(i); if ( it != s.end() ) cout << "found " << i << " at:" << to_string(s.begin(), it, s.end()) << endl; else cout << i << " not found." << endl; } } void std_multiset() { cout << "multiset" << endl; multiset<int> s; int i; for ( i = 0; i < 4; ++i ) s.insert(i); for ( i = 0; i < 3; ++i ) s.insert(i); for ( i = 0; i < 2; ++i ) s.insert(i); cout << to_string(s.begin(),s.end()) << endl; typedef multiset<int>::iterator iter; pair<iter,iter> range; for ( i = 0; i < 6; ++i ) { range = s.equal_range(i); if ( range.first != range.second ) { cout << "key:" << i << " at:" << to_string(s.begin(), range, s.end()) << endl; } else { cout << i << " not found." << endl; } } }