Archive for March, 2006

CppUnit 導入ガイド

Thursday, March 30th, 2006

ダウンロードとインストール

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はテストの実行モジュール・サイズを小さく抑えることができます。

batchbuild

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

Thursday, March 30th, 2006

psecpp_logo

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を定義し、電話帳の中にPersonProgrammerとを一緒に登録したいのです。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;
}
  1. データベース"array.db"を生成します。
  2. int配列をデータベース上に作成します。

    newに続く()内の引数にはそれぞれ、

    • データベース
    • 型情報
    • 要素数(1のときは省略可)

    を与えます。この例では10個のintをデータベース上に作成しています。

  3. arrayの各要素に値をセットします。
  4. arrayをデータベースから読み込むための手掛かりとなるdb_rootを作ります。db_rootは文字列で識別され、ひとつのデータベースにいくつものdb_rootを作ることができます。
  5. db_rootarrayをセットします。
  6. データベースをsave()することで、arrayの内容がデータベースに固定されます。
  7. データベースをクローズします。

これで書き込みは完了です。作成されたデータベース "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;
}
  1. データベースをオープンします。
  2. 書き込み時に作成したdb_rootを見つけ、
  3. 格納されたint配列をたぐり寄せます。
  4. arrayの内容を出力します。
  5. データベースをクローズします。

書き込み/読み込み時、arrayの各要素にアクセスしている部分に注目してください。通常の配列要素のアクセスとまったく同じです。データベースにアクセスしているとは思えないでしょう?
PSEのスゴいところは"迫真の言語透過性"なんですね。


src

クラスの永続化

では次にユーザ定義型(クラス)の永続化です。

/* --- 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(クラス名)

を列挙します。


src

ポインタの永続化

冒頭に示したクラス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");

ならばヒープ上に確保されます。うまくできているものです。


src

さて、それではここまでのまとめとして、

object

なる関係を持つPerson*[],Link<Person>,Stack<Person>を永続化しましょう。ソースは少々長くなるので書き込み/読み込みのmainのみを示します。全ソースはsrc

/* --- 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アプリケーションが作れますよ!

 

編集部注

  1. ^ 2010年2月現在、ObjectStore日本プログレス株式会社の製品となっているようです。

[Java入門]3.10 まとめ

Thursday, March 30th, 2006

これで、Java言語でプログラミングをする際に必要となる基礎知識は一通り紹介しました。この章の内容については、後半部分、特にサブクラスを使うあたりから、少々難しいと感じられた方も多いでしょう。しかし、すべてが理解できなかったと心配することはありません。他の章を読み、また、時には本章に戻るなどして、ゆっくりと理解を深めていけばいいのです。

もちろん、本章では十分に説明していない部分もありますし、実際にプログラミングをするにあたって知っておいた方がいいノウハウなどにもほとんど触れていません。しかし、そういったことは、本書の第7章の言語仕様を参照したり、その他の章を読みながらプログラミングを実践するなかで段々と理解し、身についていくものなので、心配しないでください。

次章はいよいよお待ちかね、アプレットプログラミングです。

prev

[Java入門]3.9 車とガソリンタンクの共通点

Thursday, March 30th, 2006

車に乗っていてガス欠になってしまったことはありませんか? バイクならスタンドまで手で押していってもいいのですが、さすがに車を押していくのはちょっと無理です。

こんなとき、何かガソリンを入れられるような缶などがあれば、それだけをもってガソリンスタンドを探しにいけますね。とりあえずそのスタンドにたどり着けるくらいのガソリンを買ってくればいいのですから。

そうすると、車だけでなく、缶やポリタンク等もガソリンスタンドを利用できるということになります。前節で定義したガソリンタンクのクラスを思い出してみてください。そもそもこのタンククラスは、車クラスからガソリンを入れるという機能を取り出したものですから、当然のことながらガソリンを入れることができます。

同じ様に、缶クラスやポリタンククラスを定義してみると、すべてガソリンを入れるという同じ機能を持っていることになるでしょう。

ここで、実際にガソリンを入れるガソリンスタンドの立場になってみると、形態は違ってもみんな同じお客さんですから、同じ様な手順で扱いたいですね。車クラスとタンククラスではこの手順は共通しており、ガソリンタンクの残量を確認するときには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

prev next

[Java入門]3.8 ガソリンタンクを独立させる

Thursday, March 30th, 2006

これまで、ガソリンは車に入れるものと単純に考えてきました。しかし実際の車にはガソリンタンクが合って、ガソリンはそのガソリンタンクに入れておくのが普通です。

ガソリンタンクは車の内部にあって、そこにガソリンを入れるための給油口を用意したり、ガソリンの残量を示すメーターを取り付けているわけです。ガソリンがどのくらい残っているのかとか、後何リットル入るかなどの情報は、実は車自体の情報ではなく、ガソリンタンクの情報なのです。

しかし、ガソリンを入れるときには、普通「車に入れる」と考えます。実際、ガソリンスタンドに行って、「この車の内部にあるガソリンタンクを満タンにしてください」という人はまずいないでしょう。

こうして考えてみると、ガソリンタンクと言うのもまたクラスとしてとらえることができるでしょう。用量が決まっていて、今どのくらいはいっているのかを情報として持っており、満タンかどうかを調べることができて、もちろんガソリンを入れることもできる。変数やメソッドが思い浮かんでは来ませんか?

さらに、ここまでの例で車の情報や機能として考えていたものは、実は車のガソリンタンクが持っている情報や機能だったとも言えるかもしれません。

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

ところで賢明な読者の方々は、ガソリンスタンドにも実はタンクがあるということに気づかれていることでしょう。まさしくその通りです。ガソリンスタンドの定義も、タンククラスを利用するように書き換えることができます。これは読者への課題ということにしますので、是非チャレンジしてみてください。

prev next

STL Samples : <stack>

Thursday, March 30th, 2006


スタック

Cstack

SOURCE

#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>

Thursday, March 30th, 2006


双方向リスト

Clist

SOURCE

#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>

Thursday, March 30th, 2006

可変長配列

classvector

source

#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>

Thursday, March 30th, 2006

重複を許さない辞書

classmap

重複を許す辞書

classmultimap

source

#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>

Thursday, March 30th, 2006

重複を許さない集合

classset

重複を許す集合

classmultiset

source

#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;
    }
  }
}