Archive for the ‘C++ 寄稿記事’ Category

C++の新しいキャスト

Monday, April 2nd, 2007

従来のキャストの問題点

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

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

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

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

  1. 型変換
    // int から double へ
    int ival;
    double dval = (double)ival;
  2. 型変更
    // long から int* へ
    long lval;
    int* iptr = (int*)lval;
  3. const外し
    // const int* から int* へ
    const int* ciptr;
    int* iptr = (int*)ciptr;

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

(型)式

です。つまりコードからは”何が目的でキャストしたのかはっきりしない“のですね。

4種類の新しいキャスト

キャストは使わずに済ませられるならそれに越したとはないのですが、どうしても使わざるを得ないシチュエーションは少なからず存在します。
ならばせめて少しでも安全にキャストするために新たに定められたキャスト構文が、以下に挙げるstatic_cast / reinterpret_cast / const_cast / dynamic_cast です。

static_cast

static_cast<type>(expr)

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

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

reinterpret_cast

reinterpret_cast<type>(expr)

reinterpret_casttype(expr)が許されるなら、exprtypeに単にキャストします。

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

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

const_cast

const_cast<type>(expr)

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

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

dynamic_cast

dynamic_cast<type>(expr)

dynamic_castは基底クラスへのポインタ(or 参照)から派生クラスへのポインタ(or 参照)への型保証キャストを行ないます。

上記3種のキャストはコンパイル時にキャストしますが、dynamic_castは実行時に型の検査が行なわれ、変換不可能であれば0を返します。

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

Base base;
Derived derived;

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

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

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

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

クロス・キャスト
/*
 * Shape と Drawable は派生関係にない
 */
class Shape {
 ...
};

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

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

Shape* shape = new Circle;
/* 従来のキャスト (Drawable*)shape では、
 * 場合によっては暴走する */
Drawable* drawable = dynamic_cast<Drawable*>(shape);
if ( drawable )
  drawable->draw();
抽象基底クラスからのダウン・キャスト
class Machine {
  ...
};

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

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

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

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

※注意

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

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

新しいキャストは従来のキャストよりはるかに安全です。
また、grepなどで”_cast”をキーにすればキャストした個所を簡単に検索できるという副次的な効果もあります。

(more…)

sizeofの不思議

Monday, April 2nd, 2007

はじめに

Cではsizeofによって構造体がメモリ上で占める大きさ(バイト数)を知ることができます。

struct s{
  int x;
  int y;
};
...
cout << sizeof(s) << endl;

僕の愛用する処理系、Visual C++ 6.0では 8が得られました。intひとつにつき4byteを消費するからでしょう。

それではC++でのclassの大きさはどうでしょう。内包するメンバ変数それぞれの占めるバイト数の総和になるのでしょうか。

class c {
  int x;
  int y;
};
...
cout << sizeof(c) << endl;

答は…やはり 8 です。でもね、C++ではいつもこうなるとは限らないのですよ。

「C++はCよりデカい」と言われることがあります。その理由のひとつがここに明らかになります。

仮想関数

上記class cにメンバ関数を追加します。

class c {
  int x;
  int y;
public:
  int sum();
};
...
cout << sizeof(c) << endl;

結果は 8 でした。メンバ関数を追加してもサイズに変化はないようです。

ところが、仮想関数を追加すると…

class c {
  int x;
  int y;
public:
  virtual int sum();
};
...
cout << sizeof(c) << endl;

結果は12。4バイト増加しています。まるでメンバ関数へのポインタがクラス内にこっそり追加されたかのようです。
ならば仮想関数が2つあったら8バイト増えるのかな…

class c {
  int x;
  int y;
public:
  virtual int sum();
  virtual int sub();
};
...
cout << sizeof(c) << endl;

依然として 12 のままです。実体を伴わない純粋仮想関数を追加したら…

class c {
  int x;
  int y;
public:
  virtual int sum() =0;
};
...
cout << sizeof(c) << endl;

…ふむ、純粋仮想関数を追加してもクラスの大きさは 12 ですね。

これらのことから、クラスに仮想関数がひとつ以上あるとき、クラスの大きさは 4 バイト増加すると思われます。

何故でしょう。その理由は仮想関数が実行されるときのからくりにあります。

仮想関数を含むクラスを宣言すると、そのクラスの中メンバ変数'vptr'がひとつ、こっそり追加されます。これは"仮想関数へのポインタ配列'vtbl'へのポインタ"です。
vtblはクラス内にある仮想関数のポインタを配列内に納めたもので、仮想関数を含むクラス毎にひとつ作られます。仮想関数がコールされるとき、vptrの指す配列vtblの特定の位置から仮想関数へのポインタを取り出し、そこをコールします。

このようなからくりのために、仮想関数を含むクラスは、仮想関数のないクラスより少し大きいのです。

継承

継承したときのクラスの大きさはどうでしょうか。

class c0 {
  int x;
  int y;
};

class c1 : public c0 {
  int a;
  int b;
};
...
cout << sizeof(c0) << endl;
cout << sizeof(c1) << endl;

結果はそれぞれ 8,16 でした、予想通りの結果です。

仮想関数を含むとどうなるでしょう。

class c0 {
public:
  int x;
  int y;
  virtual int sum();
};

class c1 : public c0 {
public:
  int a;
  int b;
  virtual int diff();
};
...
cout << sizeof(c0) << endl;
cout << sizeof(c1) << endl;

sizeof(c0)が 12だから、c1はそれにint2個分(8byte)と'vptr'(4byte)を加えて 24 …ではなくて 20 となりました。仮想関数テーブルポインタ'vptr'はクラス内にひとつあればいいからです。

cout << offsetof(c1,x) << endl;
cout << offsetof(c1,y) << endl;
cout << offsetof(c1,a) << endl;
cout << offsetof(c1,b) << endl;

の結果はそれぞれ、4, 8, 12, 16 となりました、このことから、class c1のメモリ上でのレイアウトは、

offset member
+0 c1::vptr
+4 c0::x
+8 c0::y
+12 c1::a
+16 c1::b

となっていると考えられます。

多重継承

多重継承の場合、ちょっとばかしややこしくなります。

class c0 {
public:
  int x0;
  int y0;
  virtual void f0() {}
};

class c1 {
public:
  int x1;
  int y1;
  virtual void f1() {}
};

class c2 : public c0, public c1 {
public:
  int x2;
  int y2;
  virtual void f2() {}
};
...
cout << "c0  :        " << sizeof(c0)  << endl;
cout << "c1  :        " << sizeof(c1)  << endl;
cout << "c2  : c0, c1 " << sizeof(c2)  << endl;

'仮想関数ポインタテーブル(vptr)はクラスにひとつ'であるならば、sizeof(c2)は、10,c1,c2それぞれのメンバ変数領域の総和(8×3=24byte)にvptr(4byte)を加えた28byteとなりそうです。

ところが結果は予想に反して 32となりました。vptrが2つ含まれているようです。

cout << offsetof(c2, x0) << endl;
cout << offsetof(c2, y0) << endl;
cout << offsetof(c2, x1) << endl;
cout << offsetof(c2, y1) << endl;
cout << offsetof(c2, x2) << endl;
cout << offsetof(c2, y2) << endl;

の結果: 4, 8, 16, 20, 24, 28 から察するに、c2のメモリ・レイアウトは:

offset member
+0 c2::vptr
+4 c0::x0
+8 c0::y0
+12 c1::vptr
+16 c1::x1
+20 c1::y1
+24 c1::x2
+28 c1::y2

であろうと考えられます。

仮想継承

さらに仮想継承を考えると、話はさらにややこしくなります。

class c0 {
public:
  int x0;
  int y0;
  virtual void f0() {}
};

class c1 : virtual public c0 {
public:
  int x1;
  int y1;
  virtual void f1() {}
};

class c2 : virtual public c0 {
public:
  int x2;
  int y2;
  virtual void f2() {}
};

class c3 : public c1, public c2 {
public:
  int x3;
  int y3;
  virtual void f3() {}
};
...
cout << "c0  :        " << sizeof(c0)  << endl;
cout << "c1  : c0     " << sizeof(c1)  << endl;
cout << "c2  : c0     " << sizeof(c2)  << endl;
cout << "c3  : c1, c2 " << sizeof(c3)  << endl;

sizeof(c3)は 52 となりました。なんとも不思議な結果です。上記の各クラスから仮想関数を抜き取ってみましょう。

class c0 {
public:
  int x0;
  int y0;
};

class c1 : virtual public c0 {
public:
  int x1;
  int y1;
};

class c2 : virtual public c0 {
public:
  int x2;
  int y2;
};

class c3 : public c1, public c2 {
public:
  int x3;
  int y3;
};
...
cout << "c0  :        " << sizeof(c0)  << endl;
cout << "c1  : c0     " << sizeof(c1)  << endl;
cout << "c2  : c0     " << sizeof(c2)  << endl;
cout << "c3  : c1, c2 " << sizeof(c3)  << endl;

このとき、sizeof(c3)は 40となりました。仮想継承したベースクラスc0の各メンバは、c3内にはひとつしかないはずなので、予想されるサイズは (8×4=)32
であるはずなのに、8byte余計な領域を必要としています。仮想関数を元に戻し、

cout << offsetof(c3, x1) << endl;
cout << offsetof(c3, y1) << endl;
cout << offsetof(c3, x2) << endl;
cout << offsetof(c3, y2) << endl;
cout << offsetof(c3, x3) << endl;
cout << offsetof(c3, y3) << endl;

を実行すると、それぞれ 8, 12, 24, 28, 32, 36
となりました。この結果から得られるメモリ・レイアウトは:

offset member
+0 ???
+4 ???
+8 c1::x1
+12 c1::y1
+16 ???
+20 ???
+24 c2::x2
+28 c2::y2
+32 c3::x3
+36 c3::y3
+40 ???
+44 ???
+48 ???

'???'の領域のうち、少なくとも2つはc0::x0とc0::y0でしょう。

「C++はCよりデカい」か?

結論から言うと、仮想関数、多重継承、仮想継承などなどを駆使したC++コードは確かにデカくなることがわかりました。最後の例など、Cでなら32byteで済むところが、20byteも余計にメモリを消費します。インスタンスひとつにつき20byteのメモリを余計に消費するのですからアプリケーション全体では無視できないでしょうね。

しかし、だからといってC++コードから仮想関数を排除するのは決して得策とは思えません。仮想関数と同じふるまいを仮想関数なしで実現するには、オブジェクトの本来の型を識別するためのIDをメンバとして用意しなければなりませんし、関数呼び出しのたびに、IDによる処理の振り分けを行なうことになります。結局仮想関数を用いた場合と同等のサイズになることが予想されるうえ、コードのメンテナンス性が著しく低下するでしょう。

※注意:
これはMicrosoft Visual C++ v6.0での実験結果です。
これ以外のOSや処理系では異なる結果が得られることでしょう。

おまけ : やりがちな誤り

仮想関数のお話ついでに、うっかりやってしまいそうな誤りをひとつ紹介しておきます。あなたはいくつかのクラスを設計/実装することになり、それぞれのコンストラクタ/デストラクタが確実に行われているか、画面に表示して確かめたくなりました。そこであなたはこんなコードを書きました。

class base {
public:
  base();
  virtual ‾base();
  virtual void on_ctor();
  virtual void on_dtor();
};

base::base() {
  on_ctor();
}

base::‾base() {
  on_dtor();
}

void base::on_ctor() {
  cout << "base::ctor¥n";
}

void base::on_dtor() {
  cout << "base::dtor¥n";
}

class baseはそのコンストラクタ/デストラクタの中からそれぞれ仮想関数on_ctor()/on_dtor()を呼んでいます。ですからbaseの派生クラス側でon_ctor()/on_dtor()を再定義すればいいはずです。

class c0 : public base {
public:
  virtual void on_ctor();
  virtual void on_dtor();
};

void c0::on_ctor() {
  cout << "c0::ctor¥n";
}

void c0::on_dtor() {
  cout << "c0::dtor¥n";
}

class c1 : public base {
public:
  virtual void on_ctor();
  virtual void on_dtor();
};

void c1::on_ctor() {
  cout << "c1::ctor¥n";
}

void c1::on_dtor() {
  cout << "c1::dtor¥n";
}

int main() {
  c0 x;
  c1 y;
  return 0;
}

さて、結果はどうなったでしょう…

base::ctor
base::ctor
base::dtor
base::dtor

惨澹たる結果となりました。

そう、C++ではコンストラクタ/デストラクタの中から仮想関数を呼んでも、導出クラスで再定義された関数には飛んでこないんです。

この例のように、コンストラクタ/デストラクタから直接仮想関数を呼んでいるのであればそれに気づくのにそう時間はかからないのですが、コンストラクタ/デストラクタが呼んでいる関数の中、あるいはさらにそこから呼ばれている関数…
コンストラクタ/デストラクタが終了するまでに直接的あるいは間接的に呼ばれている一連の関数の中に仮想関数が含まれていたとき、そのふるまいは意図しているものとは異なることになるのです。

コンストラクタ/デストラクタ内から仮想関数を呼び出すことはできませんから、仕方なしにコンストラクト/デストラクトと初期化/後始末を分離しなければなりません。

// ----- これならちゃんと動く,,, -----
class base {
public:
  base() {}
  virtual ‾base() {}
  void    start();
  void    stop();
  virtual void on_ctor();
  virtual void on_dtor();
};

void base::start() {
  on_ctor();
}

void base::stop() {
  on_dtor();
}

void base::on_ctor() {
  cout << "base::ctor¥n";
}

void base::on_dtor() {
  cout << "base::dtor¥n";
}

/*
 * c0, c1 はそのまま...
 */

int main() {
  c0 x; x.start();
  c1 y; y.start();
  y.stop();
  x.stop();
  return 0;
}

これがJavaだと思った通りに動いてくれます。ま、Javaにはデストラクタがありませんけど…

// ----- Trial.java -----
class base {
  base() {
    on_ctor();
  }
  void on_ctor() {
    System.out.println("base::ctor");
  }
  protected void finalize() {
    System.out.println("base::dtor");
  }
}

class c0 extends base {
  void on_ctor() {
    System.out.println("c0::ctor");
  }
  protected void finalize() {
    System.out.println("c0::dtor");
  }
}

class c1 extends base {
  void on_ctor() {
    System.out.println("c1::ctor");
  }
  protected void finalize() {
    System.out.println("c1::dtor");
  }
}

public class Trial {
  public static void main(String[] arg) throws Exception {
    c0 x = new c0();
    c1 y = new c1();
  }
}

実行結果:

c0::ctor
c1::ctor

…ほらね。

抽象データ型と Java/C++ そして COM/CORBA

Monday, April 2nd, 2007

抽象データ型とは…

  • 抽象データ型
  • 継承
  • 多態

をオブジェクト指向の三本柱などと称しています。その中でも抽象データ型(あるいはデータの抽象化)はオブジェクト指向の最も基本的で重要な概念ではないかと考えます。

"データを抽象化する"とは、データをそれに対して適用できる操作の集合で定義することです。

簡単な例として"カウンタ"を考えてみましょう。カウンタには3つの操作:

  • +1する (increment)
  • -1する (decrement)
  • 現在値を取得する

を提供させることにします。

さて、このカウンタをCで実現するとどうなるでしょうか…

typedef struct {
  long value_;
} Counter;

Counter* counter_create();
void     counter_incr(Counter*);
void     counter_decr(Counter*);
long     counter_value(const Counter*);
void     counter_release(Counter*);

/* ---- ここから実装部。Counterの利用者には見せない ---- */

Counter* counter_create() {
  Counter* c = (Counter*)malloc(sizeof(Counter));
  c->value_ = 0;
  return c;
}

void     counter_incr(Counter* c) { ++c->value_; }

void     counter_decr(Counter* c) { --c->value_; }

long     counter_value(const Counter* c) { return c->value_; }

void     counter_release(Counter* c) { free(c); }

…こんな感じになりますか。C++の入門書の中には、

データ(構造体) + アルゴリズム(関数) = オブジェクト(クラス)

と説明されたものもあります。つまり、構造体に、それに適用される操作を付け加えたものがクラスだ、というのです。

class Counter {
private:
  long value_;
public:
  Counter();
  ~Counter();
  void incr();
  void decr();
  long value() const;
};

/* --- ここから実装部。Counterの利用者には見せない --- */

Counte::Counter() : value_(0) {}

Counte::~Counter()  {}

void Counter::incr() { ++value_; }

void Counter::decr() { --value_; }

long Counter::value() const { return value_; }

Counterのメンバ変数value_は、利用者に勝手にアクセスさせないようprivate部に置きました。

カウンタの利用者にしてみればprivateメンバに何が定義されているかなんて(どうせアクセスできないのだから)知る必要もありません。+1できて、-1できて、値を取得できるということだけで十分です。

カウンタの実装者にしても同様です。+1する、-1する、値を返す、の3つの機能をきちんと実装できるのなら、privateメンバはその実現のためになら何を定義しようがかまわない。メソッドが呼ばれるたびに電話回線を通してはるか遠くにあるデータベースを更新・参照したっていいでしょうよ。

結局カウンタについて利用者と実装者との間できめておかなければならないのは、カウンタに対する操作だけなんです。

"Xに対して何ができるか"で定義したX、それが抽象データ型なんですね。

Javaにおける抽象データ型

Javaは、抽象データ型を直接サポートするキーワードinterfaceを持っています。

public interface Counter {
  void incr();
  void decr();
  int  value();
}

interfaceはclassではありません。実体が存在しません。Counterの実装者はinterfaceを実装(implements)するclassを作ります。

class CounterImpl implements Counter {
  private int value_;
  public CounterImpl() { value_ = 0; }
  // interface Counter を実装する
  public void incr() { ++value_; }
  public void decr() { --value_; }
  public int value() { return value_; }
}

利用者にCounterImplの存在を気づかせないために、Counterを生成するクラスCounterFactoryを用意しましょう。

public class CounterFactory {
  public static Counter create() { return new CounterImpl(); }
}

CounterFactory.create()はCounterImplをnewし、それをCounterとして返します。利用者にはCounterFactory.create()で手に入れたオブジェクトが実際にはCounterImplだということを隠しています。それでいいんです。利用者が欲しがっているのはCounterImplではなく、Counterに対する操作なのですから。

public class Client {
  public static void main(String[] arg) {
    Counter c = CounterFactory.create();
    for ( int i = 0; i < 3; ++i ) {
      c.incr();
      System.out.println("value= "+c.value());
    }
  }
}

Clientをアプレット化するとこんな感じになります。[1]

src

C++における抽象データ型

残念ながらC++ではJavaのinterfaceのような抽象データ型を直接にはサポートしていません。が、それと同等のことなら可能です。一切のメンバ変数を持たず、そして全メンバ関数を純粋仮想関数とするのです。

class Counter {
public:
  virtual ~Counter() {}
  virtual void incr() =0;
  virtual void decr() =0;
  virtual long value() const =0;
};

そしてこのCounterから派生したCounterImpl、およびCounterFactoryを作りましょう。

class CounterFactory {
public:
  static Counter* create();
  static void     release(Counter*);
};

/* --- ここから実装部。Counterの利用者には見せない --- */

class CounterImpl : virtual public Counter {
private:
  long value_;
public:
   CounterImpl() : value_(0) {}
   virtual void incr() { ++value_; }
   virtual void decr() { --value_; }
   virtual long value() const { return value_; }
};

Counter* CounterFactory::create() {
  return new CounterImpl;
}

void CounterFactory::release(Counter* c) {
   delete c;
}

利用者(Client)は以下のようになります。

using namespace std;

int main() {
  adt::Counter* c = adt::CounterFactory::create();
  for ( int i = 0; i < 3; ++i ) {
    c->incr();
    cout << "value= " << c->value() << endl;
  }
  adt::CounterFactory::release(c);
  return 0;
}

src

言語を越えて…

抽象データ型はその利用者と実装者との間の申し合わせです。これを発展させると、抽象データ型を利用者と実装者双方が認識できる、すなわち実装者はそのインタフェースをサポートするオブジェクトを生成し、利用者はそのインタフェースを提供するオブジェクトを手に入れて利用することができるなら、利用者と実装者が互いに異なる言語で書かれていてもいいはずです。それを実現したのがCOM、そしてCORBAです。

COM (Component Object Model)

Visual C++ 6.0 ATLを使ってCOMサーバ(実装側)を作ってみましょう。

Visual C++の”ATL COMAppWizard”を使えばCOMサーバをとっても簡単に作れます。

CounterのインタフェースICounterを作ってできたファイルadt.idlを以下に示します。

...
  interface ICounter : IDispatch {
    [id(1), helpstring("+1する")] HRESULT incr();
    [id(2), helpstring("-1する")] HRESULT decr();
    [propget, id(3), helpstring("値を返す")] HRESULT value([out, retval] long *pVal);
  };
...
  coclass Counter {
    [default] interface ICounter;
  };
...

adt.idlの中に、incr,decr,valueをサポートするインタフェースICounterと、ICounterをインタフェースとして持つクラスCounterがあることがわかるでしょう。

以下に示すのは最終的にできあがったCounterの実装部CCounterです。

class ATL_NO_VTABLE CCounter :
  public CComObjectRootEx<CComSingleThreadModel>,
  public CComCoClass<CCounter, &CLSID_Counter>,
  public IDispatchImpl<ICounter, &IID_ICounter, &LIBID_ADTLib> {
public:
  CCounter() { value_ = 0; }

DECLARE_REGISTRY_RESOURCEID(IDR_COUNTER)
DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CCounter)
  COM_INTERFACE_ENTRY(ICounter)
  COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

public:
  STDMETHOD(get_value)(long *pVal);
  STDMETHOD(decr)();
  STDMETHOD(incr)();
private:
  long value_;
};
/* --- implementation --- */

STDMETHODIMP CCounter::incr() {
  ++value_;
  return S_OK;
}

STDMETHODIMP CCounter::decr() {
  --value_;
  return S_OK;
}

STDMETHODIMP CCounter::get_value(long *pVal) {
  *pVal = value_;
  return S_OK;
}

src

これをコンパイルすればadt.dllができあがり、レジストリに登録されます。これでCOMをサポートする言語ならなんであれ、利用者はCounterを使うことができるはずです。

Visual BasicからCounterを使ってみましょうか。VisualBasicのメニューから”プロジェクト|参照設定”ることで、VisualBasicがCounterのインタフェースを認識します。

フォーム上にテキストボックス(txtValue)とボタンをふたつ(btnIncr,btnDecr)配置して:

Option Explicit

Private c As Counter

Private Sub btnDecr_Click()
  c.decr
  txtValue = c.Value
End Sub

Private Sub btnIncr_Click()
  c.incr
  txtValue = c.Value
End Sub

Private Sub Form_Load()
  Set c = New Counter
End Sub

Private Sub Form_Unload(Cancel As Integer)
  Set c = Nothing
End Sub

なんとまぁ、たったこれだけで動いてしまうんです。

CORBA (Common Object Request Broker Architecture

CORBAはインタフェースを利用者と実装者が共有するという考えをもう一歩すすめ、異なるマシン/異なるOS/異なるプロセス/異なる言語による利用者/実装者を可能にしてくれます。

Orbixを使ってCounterを作ります。まずはインタフェースの定義から。

module adt {
  interface Counter {
    void incr();
    void decr();
    readonly attribute long value;
  };
};

avaのinterfaceとそっくりです。このインタフェース定義ファイルadt.idlをIDLコンパイラに食わせると、利用者/実装者それぞれのためにスタブ/スケルトンを吐いてくれます。実装者はスケルトンを基にCounterを実装し、利用者はスタブを使ってCounterにアクセスします。

/*
 * interface Counter の実装
 */
#include "Counter.hh"

namespace adt {

  class CounterImpl : public virtual CounterBOAImpl {
  private:
    CORBA::Long value_;

  public:
    CounterImpl() : value_(0) {}

    virtual void incr(CORBA::Environment &IT_env=CORBA::default_environment) ;
    virtual void decr(CORBA::Environment &IT_env=CORBA::default_environment) ;
    virtual CORBA::Long value(CORBA::Environment &IT_env=CORBA::default_environment) ;
  };

}

namespace adt {

  void CounterImpl::incr(CORBA::Environment &IT_env) {
    ++value_;
  }

  void CounterImpl::decr(CORBA::Environment &IT_env) {
    --value_;
  }

  CORBA::Long CounterImpl::value(CORBA::Environment &IT_env)  {
    return value_;
  }

}

実装者(サーバ)は(COMをレジストリに登録するように)あらかじめOrbixデーモンに登録しておきます。利用者はデーモンの仲介でCounterを手に入れ、利用します。

/*
 * クライアント
 */
#include "Counter.hh"
#include <iostream.h>

int main() {
  adt::Counter_var c;
  c = adt::Counter::_bind(":adt");
  for ( int i = 0; i < 3; ++i ) {
    c->incr();
    cout << "value= " << c->value() << endl;
  }
  return 0;
}

src

注釈
  1. ^弊社のWebサイト公開ポリシーによりアプレットは削除しております。

9章: クラス RWBTreeOnDisk の使い方

Friday, April 14th, 2006

RWBTreeOnDisk クラスは、ディスク上のファイルで B ツリーを管理するために設計されています。このクラスは、キーと値の関連付けを順序よく並べたコレクションを表わします。その順序はキーを比較することによって内部的に決定されます。キーを指定すると、値を取り出すことができます。キーの重複は許可されません。

キーは char の配列で、キーの長さはコンストラクタによって設定されます。B ツリー内の順序は、キーを外部関数 (変更可能) と比較することによって決定されます。

値の型は次のとおりです。

typedef long RWstoredValue;

値は通常、オブジェクトが格納されているファイル内の場所へのオフセットを表わします。キーを指定すると、オブジェクトが格納されている位置を見つけ、それを取り出すことができます。ただし、RWBTreeOnDisk クラスに関するかぎり、この値は特別な意味を持ちません。これを解釈するのはプログラマ次第です。

RWBTreeOnDisk クラスは、RWFileManager クラスを使って B ツリーノードの領域の割り当ておよび解放を管理します。B ツリーとデータが同じファイル内にある場合は、同じ RWFileManager を使って、オブジェクト自体の領域を管理できます。または、別の RWFileManager を使って別のファイルを管理し、B ツリーとデータを別々のファイルに格納することも可能です。

RWBTreeOnDisk クラスのメンバ関数は、メモリにおける RWBTreeDictionary クラスのメンバ関数と似ています。違う点は、キーが RWCollectable ではなく、char の配列であることです。メンバ関数には、キーと値のペアを追加する、ペアを削除する、キーに関連付けられている値を置き換える、キーに関連付けられている情報を照会する、キーと値の全ペアで順序正しく操作する、ツリーに項目数を返す、キーがツリーに含まれているかどうかを決定する、などの関数があります。

4 章: クラス RWDate の使い方

Friday, April 14th, 2006

クラス RWDate はユリウス日の数値として格納された日付を表わします。ユリウス日による表現は便利なため、ソフトウェアでは一般的に用いられています。これにより迅速な暦の計算ができ、うるう年などの詳細にわずらわされることなく、一般の日付形式間との変換を簡単に行えます。

ユリウス日の数値は、Tools.h++ で簡単に求めることができます。一般の日付をユリウス日番号に変換するために Tools.h++ が使用するアルゴリズムは、ACM の 『Communications』 Volume6、No. 8、1963 年 8 月付けの 444 ページにある「Algorithm 199」に掲載されています。

グレゴリ暦は現在ほとんど世界中で使用されており、これはローマ法王グレゴリ 13 世によって 1582 年に導入され、その後各国に広まりました。グレゴリ暦は、1752 年 9 月 14 日に英国で受け入れられ、その後アメリカにやってきました。グレゴリ暦の由来を説明したのには理由があります。グレゴリ暦の導入以前の日付に対する RWDate は、グレゴリ方式から推定して求めることしかできないのです。そのような日付に対して、RWDate の出力、またはそのメソッドを使って日や月を処理することは、予期しない結果を招くことがあります。

6章: 仮想ストリームの使い方

Friday, April 14th, 2006

各 C++ コンパイラに付属の iostream 機能は、C++ の開発者に広く理解されている機能です。その利点には、ストリームへの挿入および抽出における型保証、新しい型への拡張性、およびストリームバイトのソースおよび宛先のユーザへの透過性があげられます。これらは、streambuf クラスによって設定されます。

ただし、iostream 機構には、数多くの制限があり、フォーマット機能が特に問題になります。例えば、ostream に倍精度のデータを挿入する場合、それをバイナリとして挿入するような型保証はありません。さらに、すべてのバイトソースと宛先が streambuf のモデルに適合するわけではありません。XDR など多くのプロトコルの場合、形式は本質的にバイトストリームに結び付けられていて、切り離すことができません。

Rogue Wave の仮想ストリーム機能は、ストリームの理想的なモデルを提供することによって、これらの制限を克服しており、形式やストリームモデムによって制約を受けることがありません。Rwvios クラスは仮想ストリームクラス階層のルートにあります。これは、標準ライブラリクラス ios と同様のインタフェースを持つ抽象基底クラスです。

 class RWvios{
public:
  virtual int   eof()             = 0;
  virtual int   fail()            = 0;
  virtual int   bad()             = 0;
  virtual int   good()            = 0;
  virtual int   rdstate()         = 0;
  virtual int   clear(int v = 0)  = 0;
};

RWvios から派生したクラスは、これらの関数を定義します。

抽象基底クラス RWvistreamRWvostream は、Rwvios を継承しています。これらのクラスは、すべての基本的な組み込み型および組み込み型の配列に対して、operator&#60;&#60;()、put()、get() などの純粋仮想関数を宣言しています。

 class RWvistream : public RWvios {
public:
  virtual Rwvistream&  operator>>(char&)       = 0;
  virtual Rwvistream&  operator>>(double&)     = 0;
  virtual int          get()                   = 0;
  virtual Rwvistream&  get(char&)              = 0;
  virtual Rwvistream&  get(double&)            = 0;
  virtual Rwvistream&  get(char*, size_t N)    = 0;
  virtual Rwvistream&  get(double*, size_t N)  = 0;
  .
  .
  .
};

class RWvostream : public RWvios {
public:
  virtual Rwvostream&  operator&#60;&#60;(char)             = 0;
  virtual Rwvostream&  operator&#60;&#60;(double)           = 0;
  virtual Rwvostream&  put(char)                    = 0;
  virtual Rwvostream&  put(double)                  = 0;
  virtual Rwvostream&  put(const char*, size_t N)   = 0;
  virtual Rwvostream&  put(const double*, size_t N) = 0;
  .
  .
  .
};

RWvistreamRWvostream を継承したストリームは、ユーザにわかりやすい形式で、組み込みを特殊ストリームに格納するものです。

仮想ストリーム機能の基本的機能は、形式に関係なく仮想出力ストリームに組み込みを挿入したり、抽出したりすることにあります。つまり、空白類、コンマなどで出力をフォーマットする必要がないということです。つまり、「ここは倍精度です。都合のいい形式で保存して、戻して欲しいと言ったときにそれを正しい形式で取り出してください」と Rwvostream に効果的に指示しているわけです。

このクラスは、非常に強力で、使用する最終出力媒体または形式を一切知らなくても、ストリーム演算子を書いたり使用することができます。例えば、出力媒体はディスク、メモリ領域、あるいはネットワークのどれでもかまいません。さらに、バイナリ、ASCII、またネットワークパケットなど、どの形式にも対応しています。これらいずれの場合も、同じストリーム演算子を使用します。

付録 B: Typedef とマクロ

Friday, April 14th, 2006

定数

#define FALSE 0                         // RWBoolean 値 (defs.h)

#define TRUE 1                          // RWBoolean 値 (defs.h)

#define rwnil 0                         // nil ポインタ (defs.h)

#define RWTOOLS 0x700                   // (実際の現在のバージョン番号)
                                        // number) (tooldefs.h)

const RWoffset RWNIL = -1L;             // RWFile にはオフセットがない
                                        // (defs.h)

const size_t RW_NPOS = ~(size_t)0;      // 配列へのインデックスが
                                        // 見つからない (defs.h)

Typedef

typedef unsigned short RWClassID;       // (defs.h)      各クラスで一意

typedef int RWBoolean;                  // (defs.h)      TRUE または FALSE

typedef unsigned char RWByte;           // (defs.h)      ビットフラッグ アトム

typedef RWCollectable* RWCollectableP   // (tooldefs.h)  トークン化に必要

typedef unsigned short RWErrNo          // (defs.h)      エラー処理に使用

typedef long RWoffset;                  // (tooldefs.h)  ファイルオフセットに使用

typedef unsigned long RWspace;          // (tooldefs.h)  ファイルレコードに使用

typedef long RWstoredValue;             // (tooldefs.h)  ファイルオフセットに使用

typedef void* RWvoid;                   // (tooldefs.h)  void* の配列用

関数へのポインタ

typedef void                 (*RWapplyCollectable)    (RWCollectable*, void*);

typedef void                 (*RWapplyGeneric)        (void*, void*);

typedef void                 (*RWapplyKeyAndValue)    (RWCollectable*,
                                                      RWCollectable*, void*);

typedef void                 (*RWauditFunction)       (unsigned char, void*);

typedef void                 (*RWdiskTreeApply)       (const char*,
                                                      RWstoredValue, void*);

typedef int                  (*RWdiskTreeCompare)     (const char*, const char*,
                                                      size_t);

typedef RWBoolean            (*RWtestGeneric)         (const void*, const void*);

typedef RWBoolean            (*RWtestCollectable)     (const RWCollectable*,
                                                      const void*);

typedef RWBoolean            (*RWtestCollectablePair) (const RWCollectable*,
                                                      constRWCollectable*,void*);

typedef RWCollectable*       (*RWuserCreator)         ();

Enumerations

enum RWSeverity {RWWARNING, RWDEFAULT, RWFATAL}

以下は、関連するクラスのメンバ関数やコンストラクタの動作を変更します。太字で示されている値は、デフォルト値です。

RWCString::enum stripType       {leading,trailing,both}        // 文字を剥ぎ取る場所

RWCString::enum caseCompare     {exact, ignoreCase}            // 比較中に大文字小文字
                                                                  // の区別をしない
RWCString::enum scopeType       {one, all}                     // 置換する部分文字列
                                                                  // の数

RWBTreeOnDisk::enum styleMode   {V6Style, V5Style}             // ファイル形式

RWBTreeOnDisk::enum createMode  {autoCreate, create}           //(再使用,新規作成)
                                                                  // ファイルの B-ツリー

RWeostream::enum Endian         { LittleEndian,                   // コンストラクタ
                                BigEndian, HostEndian }

                                                                  // 引数

RWLocale::enum CurrSymbol       { NONE, LOCAL, INTL }          // 「asString」メソッド
                                                                  // で使用

RWWString::enum stripType       {leading,trailing,both}        // 文字を剥ぎ取る場所

RWWString::enum caseCompare     {exact, ignoreCase}            // 比較中に大文字小文字
                                                                  // の区別をしない

RWWString::enum scopeType       {one, all}                     // 置換する部分文字列
                                                                  // の数

Tools.h++ の公開マクロ

これらのマクロは、Tools.h++ API の一部としてユーザが使用できるように、定義されています。

collect.h ファイル

// マクロの本体は記載されていない。『Tools.h++ Class Reference』のRwcollectable
// およびこのマニュアルを参照。
#define RWDECLARE_ABSTRACT_COLLECTABLE(className)
#define RWDEFINE_ABSTRACT_COLLECTABLE(className)
#define RWDECLARE_COLLECTABLE(className)
#define RWDEFINE_COLLECTABLE(className,id)
#define RWDEFINE_NAMED_COLLECTABLE(className,str)

defs.h ファイル

// RWDEBUG が定義されるときに以下のように定義する。それ以外の場合は、何も定義しない。
#define RWPOSTCONDITION(a)    assert( (a) != 0 )
#define RWPRECONDITION2(a,b)  assert( (a) != 0 )
#define RWPOSTCONDITION2(a,b) assert( (a) != 0 )
#define RWPRECONDITION2(a,b)  assert((b, (a) !=0))
#define RWPOSTCONDITION2(a,b) assert((b, (a) !=0))
#define RWASSERT(a)           assert( (a) != 0 )

edefs.h ファイル

// マクロの本体は記載されていない。第 14 章「永続性」を参照。
#define RWDECLARE_PERSISTABLE_IO(CLASS,ISTR,OSTR)
#define RWDECLARE_PERSISTABLE_TEMPLATE_IO(TEMPLATE, ISTR, OSTR)
#define RWDECLARE_PERSISTABLE_TEMPLATE_IO_2(TEMPLATE, ISTR, OSTR)
#define RWDECLARE_PERSISTABLE_TEMPLATE_IO_3(TEMPLATE, ISTR, OSTR)
#define RWDECLARE_PERSISTABLE_TEMPLATE_IO_4(TEMPLATE, ISTR, OSTR)
#define RWDECLARE_PERSISTABLE(CLASS)
#define RWDECLARE_PERSISTABLE_TEMPLATE(TEMPLATE)
#define RWDECLARE_PERSISTABLE_TEMPLATE_2(TEMPLATE)
#define RWDECLARE_PERSISTABLE_TEMPLATE_3(TEMPLATE)
#define RWDECLARE_PERSISTABLE_TEMPLATE_4(TEMPLATE)

epersist.h ファイル

// マクロの本体は記載されていない。第 14 章「永続性」を参照。
#define RWDEFINE_PERSISTABLE_IO(CLASS,ISTR,OSTR)
#define RWDEFINE_PERSISTABLE_TEMPLATE_IO(TEMPLATE,ISTR,OSTR)
#define RWDEFINE_PERSISTABLE_TEMPLATE_IO_2(TEMPLATE,ISTR,OSTR
#define RWDEFINE_PERSISTABLE_TEMPLATE_IO_3(TEMPLATE,ISTR,OSTR)
#define RWDEFINE_PERSISTABLE_TEMPLATE_IO_4(TEMPLATE,ISTR,OSTR)
#define RWDEFINE_PERSISTABLE(CLASS)
#define RWDEFINE_PERSISTABLE_TEMPLATE(TEMPLATE)
#define RWDEFINE_PERSISTABLE_TEMPLATE_2(TEMPLATE)
#define RWDEFINE_PERSISTABLE_TEMPLATE_3(TEMPLATE)
#define RWDEFINE_PERSISTABLE_TEMPLATE_4(TEMPLATE)

strmshft.h ファイル

// 便利なマクロ
#define RW_PROVIDE_DVSTREAM_INSERTER(DerivedOstream,vstreamable)
#define RW_PROVIDE_DVSTREAM_EXTRACTOR(DerivedIstream,vstreamable)

tphasht.h、tvhasht.h、tphdict.h、tvhdict.h、tphmmap.h、tvhmmap.h、tphset.h、tvhset.h
の各ファイル

// 現在のコンパイラと ANSI 準拠のコンパイラ間で移植可能なコードを
// 書く場合に便利。
// テンプレートを参照
#define RWDefHArgs(T) ,RWTHasher<T>,equal_to<T>

In files tpsrtvec.h, tvsrtvec.h

// 現在のコンパイラと ANSI 準拠のコンパイラ間で移植可能なコードを
// 書く場合に便利。
// テンプレートを参照
#define RWDefCArgs(T) ,less<T>

Smalltalk 標準インタフェース

(RW_STD_TYPEDEFS を定義することでアクティブになる)

typedef     RWBag                         Bag;
typedef     RWBagIterator                 BagIterator;
typedef     RWBinaryTree                  SortedCollection;
typedef     RWBinaryTreeIterator          SortedCollectionIterator;
typedef     RWBitVec                      BitVec
typedef     RWCollectable                 Object;    // All-too-common type!
typedef     RWCollectableDate             Date;
typedef     RWCollectableInt              Integer;
typedef     RWCollectableString           String;
typedef     RWCollectableTime             Time;
typedef     RWCollection                  Collection;
typedef     RWHashDictionary              Dictionary;
typedef     RWHashDictionaryIterator      DictionaryIterator;
typedef     RWIdentityDictionary          IdentityDictionary;
typedef     RWIdentitySet                 IdentitySet;
typedef     RWOrdered                     OrderedCollection;
typedef     RWOrderedIterator             OrderedCollectionIterator;
typedef     RWSequenceable                SequenceableCollection;
typedef     RWSet                         Set;
typedef     RWSetIterator                 SetIterator;
typedef     RWSlistCollectables           LinkedList;
typedef     RWSlistCollectablesIterator   LinkedListIterator;
typedef     RWSlistCollectablesQueue      Queue;
typedef     RWSlistCollectablesStack      Stack;

A.1 Tools.h++ コレクションクラスの選択

Friday, April 14th, 2006

選択チャートには、コレクションに格納するデータについての質問が含まれています。選択チャートにざっと目を通すことにより、作成するプログラムとそのデータにどの Tools.h++ コレクションが最適であるかが分かります。

A.1.1 選択チャートの使い方

選択チャートを見やすくするために、各選択肢の質問は簡潔になっています。次に、選択肢の質問について解説します。

コレクション内のデータの順序には意味がありますか?

コレクションの中には、コレクション内のデータの位置を制御できるものがあります。例えば、配列やベクトル、およびリンクリストはデータを「順序付きで」提示します。特定の順序で、または数値インデックスに基づいてデータにアクセスする必要がある場合、順序には意味があります。

重複項目を許可しますか?

コレクションの中には、コレクション内にすでに存在している項目と等しい項目を挿入できないものがあります (通常、この種のコレクションはセットと呼ばれます)。別の種類のコレクションには、複数の同一項目を保持する様々な方法があり、重複項目の挿入を許可します。Tools.h++ コレクションには、重複を調べる機構と重複項目を保持する機構の両方が備えられています。

順序付けは本質的なものですか、それとも外部的なものですか?

コレクション内のデータがそれを挿入する方法によって制御される場合は、「順序が外部的に決定される」と言います。例えば、ベクトルやリンクリストは、外部的に順序付けられています。データがコレクションによって使用されるアルゴリズムによって決定された位置に格納される場合は、順序付けは「本質的」です。例えば、格納されたベクトルやバランスツリーは本質的な順序を持ちます。

外部キーによってデータにアクセスしますか?

値とは異なるキーを基準にして値にアクセスする場合、「外部キーによってデータにアクセスする」と言います。例えば、「電話番号リスト」は、電話番号というデータを、名前の形式をとったキーに関連付けます。逆に、「委員会メンバのリスト」は、名前の形をとったデータだけになります。情報を取得するためには、キーを必要としません。

数値インデックスでデータにアクセスしますか?

配列またはベクトルに格納されているオブジェクトには、数値インデックスでアクセスします。例えば、数値インデックス「12」を用いてオブジェクトを見つけ、12
という位置にあるオブジェクトにアクセスします。

自身との比較によってデータにアクセスしますか?

明示的なインデックスやキーのいずれとも一緒に格納されていないデータは、検索データとコレクション内のデータの一致を探すことによって見つけることができます。前述した委員会メンバのリストは、この種類のデータの例です。Set や Bag は自身との比較によってアクセスするコレクションの例です。

自身との比較によってデータにアクセスする場合は、どの種類の一致基準を使用するかを知る必要があります。一致には、あるオブジェクトを別のオブジェクトと直接比較する「等値性」を基準にすることも、オブジェクトのアドレスを比較し、オブジェクトが同一であるかどうかを調べる「アイデンティティ」を基準にすることもできます。

最適のアクセス方式は、リンクノードによるものですか?

リンクノードを利用するコレクションは、通常リストと呼ばれます。リストでは、コレクションの両端にあるデータに素早くアクセスでき、コレクションの真ん中にデータを効率よく挿入することができます。ただし、コレクションの真ん中にあるデータに頻繁にアクセスする必要がある場合、リストは他のコレクションに比べてそれほど効率的ではありません。

頻繁にアクセスするデータがコレクションの両端にありますか?

処理しなければならないデータの量が分からない場合は頻繁にありますが、処理するほとんどのデータは、コレクションの最も新しいデータか、最も古いデータです。最後に追加されたデータを処理するのに最も効率的なのは、「後入れ先出し」方式を持つコレクションです。後入れ先出し (LIFO) のコンテナは「スタック」です。先入れ先出し (FIFO) 方式でデータを処理するコレクションは、「待ち行列」と呼ばれます。最新データと最古データの両方に効率的にアクセスできるコレクションは、「両頭列」あるいは Deque (Double Ended Queue) と呼ばれます。

リンクリストの場合-データにリストの一方からだけ、または両方からアクセスする必要がありますか?

一重リンクリストはサイズが小さいのですが、リストの「正面」からだけアクセスできます。二重リンクリストは、より柔軟性のあるアクセス方式を持ちますが、格納されている各オブジェクトごとに追加のポインタを必要とします。

数値インデックスでアクセスするコレクションの場合-コレクションを自動的にサイズ変更する必要がありますか?

コレクションに格納する項目の最大数があらかじめ分かっている場合は、固定サイズのコレクションを選択する方がより効率的に挿入と削除を行うことができます。反対に、ほとんど無制限に拡張する必要がある場合は、現在格納しているデータ量に合わせて自動的にサイズを調整するコレクションを選択するべきです。

A.1.2 その他の選択基準

どのコレクションを選択するかは、これまでの経験や直観を含め、数多くの要素に依存します。選択チャートの質問の他に、どのコレクションを選択するかを決める上で次の質問も考慮してください。

複数コレクションで単一オブジェクトを保持する必要がありますか?

「はい」の場合は、ポインタベースのコレクションを使用します。

コピーするのに時間のかかるオブジェクトを収集しますか?

「はい」の場合は、ポインタベースのコレクションを使用します。

ポインタベースのコレクションを使用しなければならない理由がありますか?

「いいえ」の場合は、値ベースのコレクションを使用します。

コレクション内のオブジェクトの順序を外部的に制御しますか?

「はい」の場合は、ベクトルやリストなどのシーケンスコレクションを使用します。

挿入後、コレクション内の項目が変更可能である必要がありますか?

「はい」の場合は、シーケンスコレクションまたはマッピング (辞書) コレクションを使用します。マップと辞書は、不変のキーを持ちますが、値は変更可能です。

コレクションがオブジェクトの比較を基準に独自の順序を保持するようにしますか?

「はい」の場合は、セット、マップ、あるいはソート済みコレクションを使用します。

数値インデックスによってコレクション内のオブジェクトにアクセスしますか?

「はい」の場合は、シーケンスコレクションまたはソート済みコレクションを使用します。

非数値キーによって値を検索する必要がありますか?

「はい」の場合は、マップまたは辞書を使用します。

比較するオブジェクトを指定して、コレクション内のオブジェクトにアクセスしますか?

「はい」の場合は、セット、マップ、あるいはハッシュベースのコレクションを使用します。

意味のある順序付けをなしで済まし、その代わりメモリをキーによる定時間のルックアップに使いますか?

「はい」の場合は、ハッシュベースのコレクションを使用します。

必要に応じて拡張または縮小するコレクションで高速のルックアップと挿入が必要ですか?

「はい」の場合は、B-ツリー、あるいは新しい標準 C++ ライブラリを基にした連想コンテナを使用します。

メモリにデータすべてを読み込まずに、アクセスする必要がありますか?

RWBTreeOnDisk または RWTValVirtualArray を使用します。

Graphic1

image21

2.1 具体クラス

Friday, April 14th, 2006

具体クラスは、以下のクラスから構成されています。:

  • 第 3 章から第 5 章で説明されている、日付、時刻、文字列などを表わす単純クラス
  • 第 11 章で説明されているテンプレートベースのコレクションクラス
  • 第 12 章で説明されている、プリプロセッサ &#60;generic.h> を使う汎用コレクションクラス

2.1.1 単純クラス

Tools.h++ には、軽量化された単純クラスが多く含まれています。軽量化されたクラスとは、時間のかからない初期設定子とコピーコンストラクタを持つクラスを意味します。単純クラスには、RWDate (日付)、 RWTime(時刻、各種の時間帯および地域に対応)、RWCString (シングルおよびマルチバイト文字列)、RWWString (ワイド文字)、および RWCRegexp または RWCRexpr (正規表現) などがあります。これらのクラスのほとんどは、4バイト以下で保持することができ、また非常に簡単なコピーコンストラクタ(通常1ビットコピー)を持ち、仮想関数は持っていません。Tools.h++ Class Referenceを参照してください。

2.1.2 テンプレートベースのコレクションクラス

テンプレートベースのコレクションクラス (略してテンプレート) には、速度と型保証という利点があります。しかし、コードサイズを減らすためには、あまり使わないようにしてください。テンプレートは多くの異なる型で使用できますが、各型がまったく新しいクラスを生成するために、コードサイズが大きくなる可能性があります。使用コンパイラで標準 C++ ライブラリが使える場合は、標準 C++ ライブラリをもとにしている Tools.h++ テンプレートベースコレクションを使用できます。使用コンパイラで標準 C++ ライブラリを使えない場合でも、11.3.1節および 11.10 節に説明されている、サブセットのテンプレートを使用できます。

2.1.3 汎用コレクションクラス

汎用コレクションクラスは、C++ コンパイラに付属しているプリプロセッサマクロ&#60;generic.h> を使用するクラスです。このクラスは、テンプレートに対応していないコンパイラに対しては、型保証がされているために、テンプレートとほとんど同じように扱えます。このため、移植性が高いとも言えます。ただし、プリプロセッサにかなり依存しているため、この汎用コレクションクラスを含むコードで、デバッガを使用することが困難になる場合があります。さらに詳しくは、第 12 章を参照してください。

2.2 抽象基底クラス

Friday, April 14th, 2006

Tools.h++ には、抽象基底クラスといろいろな機能のフレームワークを提供する特殊化クラスが含まれています。以下の表は、機能と抽象基底クラスの関係を示したものです。抽象基底クラスの場合は、Tools.h++ Class Referenceの各クラスごとの説明に明記されています。

表 1.抽象基底クラスと機能

機能
クラス
参照箇所
Locale
RWLocale
16.4 節
時間帯
RWZone
16.4 節
仮想ストリーム
RWvistream RWvostream
第 6 章
多形永続性
RWCollectable
第 14 章
仮想ページヒープ
RWVirtualPageHeap
『Class Reference』
モデルビューコントローラ抽象化
RWModel RWModelClient
『Class Reference』