18.2 コピーオンライト
クラス RWCString, RWWString, and RWTValVirtualArray<T> の各クラスは、コピー作業を最小限にとどめるために「コピーオンライト」という技法を使用します。参照カウンタへポインタを実装しているため、この技法は速くて分かりやすい値の意味論を持つという利点があります。
次に、この技法がどのように働くかについて説明します。次のように、コピーコンストラクタを使って RWCString を別の RWCString で初期化します。
RWCString(const RWCString&);
この時、どちらかの文字列に書き込みが行われるまで、2つの文字列は同じデータを共有します。書き込みが行われる時点で、データのコピーが作成され、2つの文字列は別々になります。書き込み時にだけコピー作業を行い、読み取り専用のメモリをあまり使わない文字列のコピーを作成します。次の例では、4つのオブジェクトのうち 1 つが文字列の変更を試みるまで、これらのオブジェクトがどのように 1つの文字列のコピーを共有するかを見ることができます。
#include <rw/cstring.h>
RWCString g; // グローバルオブジェクト
void setGlobal(RWCString x) { g = x; }
main(){
RWCString a("kernel"); // 1
RWCString b(a); // 2
RWCString c(a); // 3
setGlobal(a); // まだ "kernel" のコピーは 1 つ! // 4
b += "s"; // ここで b は独自のデータを持つ: "kernels" // 5
}
- //1 文字列 kernel を格納するためのメモリの実際の割り当てと初期化は、RWCString オブジェクト a で行われます。
- //2-//3 オブジェクト b と c が a から作成されるとき、これらは、オリジナルデータでの参照カウントをインクリメントし、戻ります。この時点で、同じデータを見ているオブジェクトが 3 つあります。
- //4 setGlobal() 関数が g の値、つまりグローバル RWCStringを同じ値に設定します。ここで、参照カウントが 4 になりますが、まだ、kernel のコピーはただ 1 つです。
- //5 最後に、オブジェクト b が文字列の値を変更しようとします。プログラムは参照カウントが 1 より大きい値であること、つまり文字列が複数のオブジェクトによって共有されていることを知ります。この時点で、文字列のクローンが作成され、変更されます。新しくコピーされた文字列の参照カウントが 1 になる一方、オリジナルの文字列の参照カウントが 3 に戻ります。
18.2.1 さらに分かりやすい例
RWCString のコピーはメモリを使わないため、ポインタを格納するのではなく、オブジェクト内部に値として格納することをお勧めします。次に比較を示すように、これにより管理が非常に簡単になります。背景色と文字色を設定できるウィンドウがあると仮定します。単純に考えた場合は、次のようにポインタで色を設定するでしょう。
class SimpleMinded {
const RWCString* foreground;
const RWCString* background;
public:
setForeground(const RWCString* c) {foreground=c;}
setBackground(const RWCString* c) {background=c;}
};
表面上は、文字列のコピーを 1つだけ作成すればいいので、この手段を使いたくなってしまうでしょう。この意味で、setForeground() を呼び出すのは効率的に見えます。ところが、よく見てみると、結果の意味論が混乱しています。foreground によって指されている文字列が変更した場合はどうなるのでしょうか? 文字色を変更するのでしょうか? このような場合、Simple クラスはどのようにして変更を知るのでしょうか? また、管理上にも問題があります。色付き文字を削除する前に、その文字列を指しているものがあるかどうかを知る必要があります。
次に、もっと簡単な方法を示します。
class Smart {
RWCString foreground;
RWCString background;
public:
setForeground(const RWCString& c) {foreground=c;}
setBackground(const RWCString& c) {background=c;}
ここでは、foreground=c という代入で値の意味論が使われています。Smart クラスが使う色は、完璧に曖昧性がありません。文字列が変更されないかぎりデータのコピーが作成されないため、「コピーオンライト」によりプロセスも効率的に行われます。次の例では、白が変更されるまで、white のただ 1 つのコピーが維持されます。
Smart window;
RWCString color("white");
window.setForeground(color); // 白への 2 つの参照
color = "Blue"; // 1 つの参照が白で、もう 1 つが青
18.1 ダイナミックリンクライブラリ
Tools.h++ クラスライブラリは、Microsoft Windows 3.X のダイナミックリンクライブラリ (DLL) としてリンクできます。DLLでは、ルーチンが実際に使用される実行時にリンクが行われます。この結果、ルーチンが必要に応じて引き出されるので、実行可能ファイルのサイズがずっと小さくなります。もう 1つの利点は、各アプリケーションごとに重複したコードを書くのではなく、多くのアプリケーションで同じコードを共有できることです。
Windows および C++ テクノロジーは急速に進化しているため、最新情報については、配布ディスクにあるファイル TOOLDLL.DOC を調べてください。
18.1.1 DLL の例
この節では、Tools.h++ の DLL を用いた Windows アプリケーションである DEMO.CPP について説明します。このプログラムのコードは、サブディレクトリ DLLDEMO にあります。このプログラムは、「Hello World」の例のような見慣れた部類のものでしょう。
図1.デモプログラムウィンドウ

ただし、実行時にウィンドウに挿入できる Drawable オブジェクトのリンクリストを保持するという点において、このプログラムは多少珍しいものとなっています。RWSlistCollectables クラスを用いて実装されているリストは、サブディレクトリ DLLDEMO にあります。この節では、読者に Windows 3.X プログラミングについての知識はあるが、C++ との関係についてはよく知らないものとして説明します。
18.1.1.1 DEMO.CPP コード
次に、Tools.h++ の DLL を用いた Windows アプリケーションである DEMO.CPP のメインプログラムを示します。
/*
* Tools.h++ DLL を使ったサンプル Windows 3.X プログラム
*/
#include "demowind.h"
int PASCAL
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR , int nCmdShow) // 1
{ // "DemoWindow" クラスのインスタンスを作成する:
DemoWindow ww(hInstance, hPrevInstance, nCmdShow); // 2
// それに項目をいくつか追加する:
ww.insert( new RWRectangle(200, 50, 250, 100)); // 3
ww.insert( new RWEllipse(50, 50, 100, 100));
ww.insert( new RWText(20, 20, "Hello world, from Rogue
Wave!"));
ww.show(); // ウィンドウを表示する // 4
ww.update(); // ウィンドウを更新する
// メッセージループを入力する:
MSG msg;
while( GetMessage( &msg, NULL, 0, 0)) // 5
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
return msg.wParam; // 6
}
次に、コードを行ごとに説明します。
- //1 これは、すべての Windows プログラムが持たなければならない Windows プログラムの開始部分です。これは、C の main 関数と同等です。
- //2 これは、 DemoWindow クラスのインスタンスを作成します。これについては後で検討します。これは、オブジェクトを挿入できる抽象ウィンドウを表わします。
- //3 ここで、DemoWindow に長方形、楕円、テキスト文字列の 3 つの異なるオブジェクトを挿入します。
- //4 これは、デモウィンドウ自体を表示して、更新するように、 DemoWindow に指示します。
- //5 最後に、ウィンドウのメインイベントのループを入力します。
- //6 DemoWindow デストラクタがウィンドウオブジェクトに割り当てられていたメモリを解放します。
18.1.1.2 DEMOWIND.H
このヘッダファイルで、DemoWindow クラスが定義されます。主な機能は、myList と呼ばれる一重リンクリストで、ウィンドウに挿入された項目のリストを管理します。DemoWindow::insert(RWDrawable*) メンバ関数により、新しい項目を挿入することができます。18.1.1.4 節で説明するように、RWDrawable クラスを継承するオブジェクトだけを挿入できます。
ウィンドウを再描画するときには、paint() メンバ関数を呼び出すことができます。リスト myList を走査して、そのリストの各項目を画面に再描画します。
次に、DEMOWIND.H を示します。
#ifndef __DEMOWIND_H__
#define __DEMOWIND_H__
/*
* デモウィンドウクラスにより、基底クラス RWDrawable を
* 継承する項目をデモウィンドウに
* 挿入できる
*/
#include <windows.h>
#include <rw/slistcol.h>
#include "shapes.h"
class DemoWindow {
HWND hWnd; // ウィンドウハンドル
HINSTANCE myInstance; // インスタンスのハンドル
int nCmdShow;
RWSlistCollectables myList; // ウィンドウにある項目のリスト
public:
DemoWindow(HINSTANCE mom, HINSTANCE prev, int);
~DemoWindow();
void insert(RWDrawable*); // 新しい項目をウィンドウに追加する
HWND handle() {return hWnd;}
int registerClass(); // 最初の登録
void paint(); // Windows プロシージャによって呼び出される
int show() {return ShowWindow(hWnd,nCmdShow);}
void update() {UpdateWindow(hWnd);}
friend long FAR PASCAL _export
DemoWindow_Callback(HWND, UINT, WPARAM, LPARAM);
};
DemoWindow* RWGetWindowPtr(HWND h);
void RWSetWindowPtr(HWND h, DemoWindow* p);
#endif
18.1.1.3 DEMOWIND.CPP ファイル
ここで、DemoWindow クラスの公開関数である DemoWindow コンストラクタ、DemoWindow デストラクタ、および insert()、registerClass()、paint()の各メンバ関数の定義を見てみましょう。詳しい説明は、コードリストの後にあります。
#include <windows.h>
#include <rw/vstream.h>
#include "demowind.h"
#include "shapes.h"
#include <stdlib.h>
#include <string.h>
/*
* 新しいウィンドウを生成する
*/
DemoWindow::DemoWindow( HINSTANCE mom, HINSTANCE prev,
int cmdShow) //1
{
myInstance = mom;
nCmdShow = cmdShow;
// 前のインスタンスがなかった場合は、クラスを登録する:
if(!prev) registerClass(); //2
hWnd = CreateWindow("DemoWindow", //3
"DemoWindow",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0,
CW_USEDEFAULT, 0,
NULL,
NULL,
myInstance,
(LPSTR)this ); // 'this' を隠す //4
if(!hWnd) exit( FALSE );
}
/*
* 自分自身を登録する。1 度だけ呼ばれる
*/
int
DemoWindow::registerClass(){ //5
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = ::DemoWindow_Callback; //6
wndclass.cbClsExtra = 0;
// 'this' ポインタを格納するための領域を要求する。
wndclass.cbWndExtra = sizeof(this); //7
wndclass.hInstance = myInstance;
wndclass.hIcon = 0;
wndclass.hCursor = LoadCursor( NULL, IDC_ARROW );
wndclass.hbrBackground = GetStockObject( WHITE_BRUSH );
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = "DemoWindow";
if( !RegisterClass(&wndclass) ) exit(FALSE);
return TRUE;
}
DemoWindow::~DemoWindow(){
// リストからすべての項目を削除する:
myList.clearAndDestroy(); //8
}
void
DemoWindow::insert(RWDrawable* d){
// 新しい項目をウィンドウに追加する:
myList.insert(d); //9
}
void
DemoWindow::paint(){ //10
RWDrawable* shape;
PAINTSTRUCT ps;
BeginPaint( handle(), &ps); //11
// リストにあるすべての項目を描画する。反復子を生成して開始する:
RWSlistCollectablesIterator next(myList); //12
// 各項目を描画しながらコレクション全体を反復する:
while( shape = (RWDrawable*)next() ) //13
shape->drawWith(ps.hdc); //14
EndPaint( handle(), &ps ); //15
}
/*
* このウィンドウクラスのコールバックルーチン
*/
long FAR PASCAL _export
DemoWindow_Callback(HWND hWnd, unsigned iMessage, //16
WPARAM wParam, LPARAM lParam)
{
DemoWindow* pDemoWindow;
if( iMessage==WM_CREATE ){ //17
// "this" ポインタを作成構造体から取得して
// それをウィンドウインスタンスに挿入する:
LPCREATESTRUCT lpcs = (LPCREATESTRUCT)lParam;
pDemoWindow = (DemoWindow*) lpcs->lpCreateParams;
RWSetWindowPtr(hWnd, pDemoWindow);
return NULL;
}
// 適切な "this" ポインタを取得する
pDemoWindow = RWGetWindowPtr(hWnd); //18
switch( iMessage ){
case WM_PAINT:
pDemoWindow->paint(); //19
break;
case WM_DESTROY:
PostQuitMessage( 0 );
break;
default:
return DefWindowProc(hWnd, iMessage, wParam, lParam);
};
return NULL;
}
void RWSetWindowPtr(HWND h, DemoWindow* p){ //20
SetWindowLong(h, 0, (LONG)p);
}
DemoWindow* RWGetWindowPtr(HWND h){ //21
return (DemoWindow*)GetWindowLong(h, 0);
}
- //1 これは、DemoWindow のコンストラクタです。このコンストラクタは、これを作成するアプリケーションインスタンスのハンドルである mom、既存のインスタンスのハンドルである prev、そしてウィンドウをアイコン表示するかどうかを要求します。これらの変数は、これまでに見たメインウィンドウプロシージャである WinMain から受け取ります。
- //2 コンストラクタが、以前のアプリケーションインスタンスが実行されているかどうかを調べ、実行されていない場合はクラスを登録します。
- //3 新しいウィンドウが作成されます。
- //4 主な機能は、現在の DemoWindow の this ポインタを格納する Windows のエキストラデータ機能の使用です。これを行うプロシージャは、やや大きくなりますが、非常に便利です。ここに入る値は、CREATESTRUCT 構造体に現われ、そこから CreateWindow 関数によって生成される WM_CREATE メッセージを取り出すことができます。
- //5 このメンバ関数は、アプリケーションの最初のインスタンスでだけ呼び出されます。
- //6 DemoWindow_Callback グローバル関数は、このウィンドウのコールバックプロシージャとして登録されています。
- //7 この Windows クラスの this ポインタのために領域を確保するように、Windows に要求します。これは、Windows ハンドルを特定の DemoWindow に関連付けるために使用されます。
- //8 このデストラクタは、clearAndDestroy() を呼び出し、myList にこれまで挿入された項目すべてを削除します。
- //9 insert(RWDrawable*) メンバ関数がウィンドウに項目を挿入します。18.1.1.4節で説明があるように、RDrawable は RWCollectable を継承するので、RWSlistCollectables::insert(RWCollectable*) を呼び出すときにキャストを行う必要はありません。myList を非公開にして、RWDrawable* 型の引数だけをとるように挿入に制約を加えることにより、確実に描画できるものだけがウィンドウに挿入されます。
- //10 これは、ウィンドウを再描画するとき呼び出される描画プロシージャです。
- //11 このウィンドウのハンドルを取得することから始めて、BeginPaint を呼び出し、PAINTSTRUCT に描画についての情報を与えます。
- //12 描画する項目のリストを走査する準備過程で、反復子が生成されます。
- //13 次に描画する項目を取得します。nil が返された場合は、描画する項目がもうないことを示すので、while ループが終了します。
- //14 各項目ごとに、drawWith 仮想関数を呼び出し、指定されたデバイスに項目を描画します。
- //15 PAINTSTRUCT が返されます。
- //16 これは、このウィンドウ向けのメッセージを処理する必要があるときに呼び出されるコールバックルーチンです。非常に便利な Borland C++ のキーワードである _export を使って、この関数が書き出されなければならないことを示します。このプロシージャを使う場合は、モジュールの定義ファイルの Export セクションに関数をリストする必要はありません。
- //17 メッセージが WM_CREATE メッセージの場合は、CreateWindow 関数によってメッセージが生成されます。このプロシージャはここで初めて使用されます。このやや変ったプロシージャを使って CREATESTRUCT から this ポインタ (// 4 ですでに挿入されている) を取って、それを Windows エキストラデータに入れます。RWSetWindowPtr 関数はあとで定義します。
- //18 RWGetWindowPtr(HWND) 関数を使って、Windows の HANDLE を与え、ポインタを適切な DemoWindow に読み出します。
- //19 WM_PAINT がすでに読み出されている場合は、paint() 関数が読み出されます。これについては、すでに検討しました。
- //20 this ポインタを Windows エキストラデータに入れるためにこの関数を使います。この目的は、Windows ハンドルを DemoWindow のインスタンスに一対一でマップすることにあります。
- //21 this ポインタを取り、返すためにこの関数を使います。
18.1.1.4 SHAPES.H の一部
この節では、RWDrawable のサブクラスについて取り上げます。RWDrawable クラスは、Tools.h++ の RWCollectable クラスから派生した抽象基底クラスで、drawWith(HDC) メンバ関数を持ちます。RWDrawable を特殊化するサブクラスには、この関数を実装してください。この関数は、供給デバイスに依存するハンドルにそれ自体を描画します。
ここでは、ただ 1 つのサブクラス、RWRectangle だけを再描画しました。RWRectangle クラスは、RWDrawable を継承しています。この関数は、長方形の角を維持するメンバデータとして Windows によって与えられた構造体 RECT を使用する点に注意してください。
class RWDrawable : public RWCollectable{
public:
virtual void drawWith(HDC) const = 0;
};
class RWRectangle : public RWDrawable{
RWDECLARE_COLLECTABLE(RWRectangle)
public:
RWRectangle() { }
RWRectangle(int, int, int, int);
// RWDrawable を継承:
virtual void drawWith(HDC) const;
// RWCollectable クラスを継承:
virtual Rwspace binaryStoreSize() const
{return 4*sizeof(int);}
virtual unsigned hash() const;
virtual RWBoolean isEqual(const RWCollectable*) const;
virtual void restoreGuts(RWvistream& s);
virtual void restoreGuts(RWFile&);
virtual void saveGuts(RWvostream& s) const;
virtual void saveGuts(RWFile&) const;
private:
RECT bounds; // 長方形の境界
};
18.1.1.5 SHAPES.CPP の一部
RWCollectable を継承したメンバ関数の定義はこの DLL デモの目的からは外れますが、完璧性を期すために以下にそのコードを示します。
#include "shapes.h"
#include <rw/vstream.h>
#include <rw/rwfile.h>
RWDEFINE_COLLECTABLE(RWRectangle, 0x1000) //1
// コンストラクタ
RWRectangle::RWRectangle(int l, int t, int r, int b) //2
{
bounds.left = l;
bounds.top = t;
bounds.right = r;
bounds.bottom = b;
}
// Drawable を継承:
void
RWRectangle::drawWith(HDC hdc) const //3
{
// Windows の呼び出しを行う:
Rectangle(hdc, bounds.left, bounds.top,
bounds.right, bounds.bottom);
}
// RWCollectable を継承:
unsigned
RWRectangle::hash() const //4
{
return bounds.left ^ bounds.top ^ bounds.bottom ^ bounds.right;
}
RWBoolean
RWRectangle::isEqual(const RWCollectable* c) const //5
{
if(c->isA() != isA() ) return FALSE;
const RWRectangle* r = (const RWRectangle*)c;
return bounds.left == r->bounds.left &&
bounds.top == r->bounds.top &&
bounds.right == r->bounds.right &&
bounds.bottom == r->bounds.bottom;
}
// 仮想ストリームから RWRectangle を復元する:
void
RWRectangle::restoreGuts(RWvistream& s) //6
{
s >> bounds.left >> bounds.top;
s >> bounds.right >> bounds.bottom;
}
// RWFile から復元する:
void
RWRectangle::restoreGuts(RWFile& f) //7
{
f.Read(bounds.left);
f.Read(bounds.top);
f.Read(bounds.right);
f.Read(bounds.bottom);
}
void
RWRectangle::saveGuts(RWvostream& s) const //8
{
s << bounds.left << bounds.top;
s << bounds.right << bounds.bottom;
}
void
RWRectangle::saveGuts(RWFile& f) const //9
{
f.Write(bounds.left);
f.Write(bounds.top);
f.Write(bounds.right);
f.Write(bounds.bottom);
}
- //1 このマクロを使ってただ一度だけコンパイルするには、RWCollectable のサブクラスがすべて必要です。詳しくは、15.2 節を参照してください。
- //2 これは、RECT 構造体を満たす RWRectangle のコンストラクタです。
- //3 これは、drawWith(HDC) 仮想関数の定義です。これは、単に適切な引数を持つ Windows 関数の Rectangle() を呼び出します。
- //4 ハッシュ表からこの RWRectangle を取り出す場合に、適切なハッシュ値を与えます。
- //5 isEqual() を適切に実装します。
- //6 この関数は、仮想ストリームから RWRectangle を取り出します。
- //7 この関数は、RWFile から RWRectangle を取り出します。
- //8 この関数は、仮想ストリームに RWRectangle を格納します。各項目を空白類で区切る必要ないことに注意してください。これは、仮想ストリームが必要に応じて行います。
- //9 この関数は、RWFile へ RWRectangle を書き出します。
ほかの形については、同様に RWEllipse と RWText を実装します。
3.8 ワイド文字列
RWWString クラスも各種の文字操作に用いるもので、ワイド文字に対して機能する点を除けば、RWCString に似ています。すべての文字が同じサイズ (wchar_t のサイズ) であるため、マルチバイト文字よりも操作が簡単です。
Tools.h++ では、マルチバイトとワイド文字列間で両方向の変換を簡単に行うことができます。前記の例 Sun で、どのように変換を行うかを示します。
#include <rw/cstring.h>
#include <rw/wstring.h>
#include <assert.h>
main() {
RWCString Sun("\306\374\315\313\306\374");
RWWString wSun(Sun, RWWString::multiByte); // マルチバイト文字をワイド文字にする
RWCString check = wSun.toMultiByte();
assert(Sun==check); // OK
return 0; }
基本的に、次の特殊な RWWString コンストラクタを使ってマルチバイト文字列をワイド文字列に変換します。
RWWString(const char*, multiByte_);
パラメータ multiByte_ は、例に示されているように唯一の可能な値 multiByte を持つ列挙子です。この変換は比較的計算量が多いため、multiByte 引数を用いることにより、間違って変換を行うことがないようにしています。toMultiByte() 関数を使う、ワイド文字列をマルチバイト文字列に戻す変換も、同様に計算量が多くなります。
RWCString が完全に ASCII 文字から構成されていることがわかっている場合は、どちらの方向の変換でも計算量を大幅に減らすことができます。これは、高位ビットの簡単な操作で変換が行えるためです。
#include <rw/cstring.h>
#include <rw/wstring.h>
#include <assert.h>
main() {
RWCString EnglishSun("Sunday"); // ASCII 文字列
assert(EnglishSun.isAscii()); // OK
// Ascii からワイド文字に変換:
RWWString wEnglishSun(EnglishSun, RWWString::ascii);
assert(wEnglishSun.isAscii()); // OK
RWCString check = wEnglishSun.toAscii();
assert(check==EnglishSun); // OK
return 0; }
文字列が完全にASCII 文字から構成されていることを確実にするために、メンバ関数 RWCString::isAscii() と RWWString::isAscii() をどのように使用したかに注目してください。RWWString コンストラクタ
RWWString(const char*, ascii_);
を使って、ASCII をワイド文字に変換します。パラメータ ascii_ は、唯一の可能な値 ascii を持つ列挙子です。
メンバ関数 RWWString::toAscii() を使って元に変換し直すこともできます。
3.7 マルチバイト文字列
RWCString クラスは、制限付きで各種のアルファベットを表わすマルチバイト文字列に対応しています (16.1 節参照)。マルチバイト文字は 2 バイト以上で構成されるので、文字列のバイト単位の長さは、文字列の実際の文字数と同じかそれ以上になる可能性があります。
RWCString にマルチバイト文字が含まれている場合は、文字数を返すメンバ関数 mbLength() を使ってください。ただし、RWCString にマルチバイト文字が含まれていないことがわかっている場合は、length() と mbLength() の結果は同じになるので、より速い length() の使用をお勧めします。次に、定義した Sun でマルチバイト文字を使う例を示します。
RWCString Sun("\306\374\315\313\306\374");
cout << Sun.length(); // "6" を出力
cout << Sun.mbLength(); // "3" を出力
Sun 内の文字列は、マルチバイトコードセットの一種である EUC (Extended UNIX Code) で表した漢字の「日曜日」です。EUC では、1 文字は 1 から 4 バイトの長さになります。この例では、文字列 Sun は6 バイトですが、3 文字から構成されています。
一般的に、マルチバイトの 2 番目のバイト以降はヌルの可能性があります。つまり、文字列のバイト単位の長さは、strlen() によって得られる長さと必ずしも一致するとは限りません。内部的に、RWCString は埋め込みヌルを無視します[1]。つまり、ヌルバイトを使用する文字セットに対して問題なく使用できます。また、RWCString::data() が常にヌルで切られた文字列を返すのに対し、文字列中にヌルが残っている可能性があることにも留意してください。実際にどのように動作するかは、次のプログラムに要約されています。
#include <rw/cstring.h>
#include <rw/rstream.h>
#include <string.h>
main() {
RWCString a("abc"); // 1
RWCString b("abc\0def"); // 2
RWCString c("abc\0def", 7); // 3
cout << a.length(); // "3" を出力
cout << strlen(a.data()); // "3" を出力
cout << b.length(); // "3" を出力
cout << strlen(b.data()); // "3" を出力
cout << c.length(); // "7" を出力
cout << strlen(c.data()); // "3" を出力
return 0; }
上記例では、2 つの異なるコンストラクタが使用されています。// 1 と // 2 のコンストラクタは、ヌルで区切られた文字列である const char* というただ 1 つの引数を持ちます。引数が 1 つであるため、これは型変換に使用できます (ARM 12.3.1)。結果の長さは、ヌルの前のバイト数で決定されます。// 3 のコンストラクタは、const char* と文字列の長さを引数として持っています。コンストラクタは、埋め込みヌルを含め、すべてのバイトをコピーします。
RWCString のバイト単位の長さは、常に RWCString::length() によってわかります。文字列には埋め込みヌルが含まれている可能性があるため、この長さは必ずしも strlen() による結果と一致するとは限りません。
インデックス付けおよびその他の演算子、基本的に size_t 型の引数を使用する関数は、すべてバイト単位で機能することを覚えておいてください。つまり、これらの演算子はマルチバイト文字列を含む RWCString では、機能しません。
注釈
- ^ ただし、マルチバイト文字列を転送するシステム関数は無視をしない可能性があります。RWCString は単にこのような関数を呼び出し、変換するだけです。
3.6 トークン化
RWCTokenizer クラスを使って、文字列を任意の空白類によって区切られたトークンに分解することができます。次に例を示します。
#include <rw/ctoken.h>
#include <rw/cstring.h>
#include <rw/rstream.h>
main(){
RWCString a("a string with five tokens");
RWCTokenizer next(a);
int i = 0;
// ヌル文字列が返されるまで継続する:
while( !next().isNull() ) i++;
cout << i << endl;
return 0;
}
出力:
5
このプログラムは、文字列中のトークン数を数えます。RWCTokenizer クラスの関数呼び出し演算子は、他の Tools.h++ の反復子と同様に、「次のトークンに進み、それを RWCSubString として返す」という意味に多重定義されています。それ以上トークンがない場合は、ヌルの部分文字列を返します。RWCSubString クラスは、部分文字列がヌルの文字列である場合に TRUE を返すメンバ関数 isNull() を持ちます。これを使って、ループから抜けることができます。RWCTokenizer については、『Tools.h++ Class Reference』を参照してください。
3.5 文字列 I/O
クラス RWCString は、iostream および Rogue Wave 仮想ストリームの両方に豊富な I/O 機能を提供します。
3.5.1 iostreams
標準の左シフトおよび右シフト演算子は、iostream および RWCString と共に動作するように多重定義されています。
ostream&operator<<(ostream& stream, const RWCString& cstr); istream&operator>>(istream& stream, RWCString& cstr);
これらの演算子のセマンティクスは、以下の演算子のセマンティックスと平行しています。
ostream&operator<<(ostream& stream, const char*); istream&operator>>(istream& stream, char* p);
これらは、コンパイラに付属の標準 C++ ライブラリによって定義されています。つまり、左シフト演算子
<< は、指定の出力ストリームにヌル区切りの文字列を書き込みます。右シフト演算子 >> は、空白類によって区切られた単一トークンを入力ストリームから RWCString に読み取り、以前の内容を置き換えます。
他の関数は、いろいろな RWCString 入力を行うことができます。[1]例えば、readline() 関数は改行によって区切られた文字列を読み取ります。文字を格納する前に空白類を省略するかどうかは、パラメータで指定できます。次の例には、空白類を省略した場合の違いが示されています。
#include <rw/cstring.h>
#include <iostream.h>
#include <fstream.h>
main(){
RWCString line;
{ int count = 0;
ifstream istr("testfile.dat");
while (line.readLine(istr)) // デフォルト値を使用:
// 空白類を省略
count++;
cout << count << " lines, skipping whitespace.\n";
}
{ int count = 0;
ifstream istr("testfile.dat");
while (line.readLine(istr, FALSE)) // NB: 省略しない
// 空白類
count++;
cout << count << " lines, not skipping whitespace.\n";
}
return 0;
}
入力:
line 1 line 5
出力:
2 lines, skipping whitespace. 5 lines, not skipping whitespace.
3.5.2 仮想ストリーム
Rogue Wave 仮想ストリームに対する文字列演算子にも対応しています。
Rwvistream& operator>>(RWvistream& vstream, RWCString& cstr);
Rwvostream& operator<<(RWvostream& vstream,
const RWCString& cstr);
これらの演算子を使用することにより、形式を知らなくても文字列を保存および復元することができます。仮想ストリームについては、第 6 章「仮想ストリームの使い方」を参照してください。
注釈
- ^ メソッド readFile()、readLine()、readString(istream&)、readToDelim()、およびreadToken() の詳細については、『Tools.h++ Class Reference』の RWCString のセクションを参照。
3.4 パターン一致
RWCString クラスは、文字列検索用の便利なインタフェースを持っています。以下の例をご覧ください。
RWCString s("curiouser and curiouser.");
size_t i = s.index("curious");
ここでは、s の中で curious の最初に一致する開始部分を検出します。比較は大文字と小文字の区別を付けて行われ、結果は i が 0 に設定されます。次の一致を検索するには、以下のようにします。
i = s.index("curious", ++i);
この結果 i は 14.に設定されます。大文字と小文字の区別をしないようにするには、次のようにします。
RWCString s("Curiouser and curiouser.");
size_t i = s.index("curious", 0, RWCString::ignoreCase);
これも、結果は i が 0 になります。
文字列に一致するパターンがない場合、index() は特殊値 RW_NPOS を返します。
3.4.1 単純な正規表現
パターン一致機能の一部として、Tools.h++ クラスライブラリでは、正規表現の検索に対応しています。正規表現の構文について詳しくは、『Tools.h++ Class Reference』の「RWCRegexp」を参照してください。正規表現を使って部分文字列を返すことができます。例えば、次のようにして、Windows メッセージ (接頭辞 WM_) と一致する文字列を抽出できます。
#include <rw/cstring.h>
#include <rw/regexp.h>
#include <rw/rstream.h>
main(){
RWCString a("A message named WM_CREATE");
// Windows メッセージと一致する正規表現を構築する:
RWCRegexp re("WM_[A-Z]*");
cout << a(re) << endl;
return 0;
}
出力:
WM_CREATE
RWCString の関数呼び出し演算子は、多重定義されており、型 RWCRegexp の引数を持ちます。これは、その正規表現と一致する RWCSubString を返すか、またはそのようなものが存在しない場合はヌルの部分文字列を返します。
3.4.2 拡張正規表現
このバージョンの Tools.h++ クラスライブラリは、POSIX 2 仕様を規準にした拡張正規表現に対応しています (参考文献を参照)。拡張正規表現は、UNIX ユーティリティの lex および awk で使用される正規表現です。正規表現の構文について詳しくは、『Tools.h++ Class Reference』を参照してください。
注意:RWCRExpr は、使用コンパイラが例外処理と標準 C++ ライブラリに対応している場合にのみ、使用可能です。
メモリによって制限されているものの、拡張正規表現には長さの制限がありません。括弧を使ってサブ正規表現をグループ化したり、シンボル記号 _ を使ってパターン一致用の拡張正規表現を作成できます。
次の例は、拡張正規表現の機能のいくつかを示したものです。
#include "rw/rstream.h"
#include "rw/re.h"
main (){
RWCRExpr re("Lisa|Betty|Eliza");
RWCString s("Betty II is the Queen of England.");
s.replace(re, "Elizabeth");
cout << s << endl;
s = "Leg Leg Hurrah!";
re = "Leg";
s.replace(re, "Hip", RWCString::all);
cout << s << endl;
}
出力:
Elizabeth II is the Queen of England. Hip Hip Hurrah!
RWCString
の関数呼び出し演算子は、多重定義されており、RWCRExpr
型の引数を持ちます。これは、表現が一致する RWCSubString
を返すか、そのような表現が存在しない場合はヌルの部分文字列を返します。
3.3 部分文字列
RWCSubString クラスは部分文字列の抽出と変更に対応しています。RWCSubString は、RWCString の様々なメンバ関数によって間接的に生成されており、最初の機会に破棄されます。そのため、公開コンストラクタはありません。
部分文字列は、いろいろな場合に使用できます。例えば、RWCString::operator() で部分文字列を作成し、それを用いて RWCString を初期化できます。
RWCString s("this is a string");
// 部分文字列から RWCString を生成する:
RWCString s2 = s(0, 4); // "this"
結果は、s の最初の 4 文字「this」をコピーした文字列 s2 になります。
また、文字列または RWCString や RWCSubString への代入で、RWSubString を左辺値 (lvalue) として 使うこともできます。
// RWCString を生成する
RWCString article("the");
RWCString s("this is a string");
s(0, 4) = "that"; // "that is a string"
s(8, 1) = article; // "that is the string"
部分文字列への代入は、厳密な演算ではないことに注意してください。そのため、代入演算子の両辺の文字数が同じである必要はありません。
3.2 辞書式順序の比較
辞書を編成する場合は、RWCString の辞書式順序の比較演算子が特に便利です。
RWBoolean operator==(const RWCString&, const RWCString&); RWBoolean operator!=(const RWCString&, const RWCString&); RWBoolean operator< (const RWCString&, const RWCString&); RWBoolean operator<=(const RWCString&, const RWCString&); RWBoolean operator> (const RWCString&, const RWCString&); RWBoolean operator>=(const RWCString&, const RWCString&);
これらの演算子は、大文字と小文字を区別します。大文字と小文字を区別しないで比較をしたい場合は、次のメンバ関数を使います。
int RWCString::compareTo(const RWCString& str,
caseCompare = RWCString::exact) const;
次に、str が辞書的順序においてそれ自体より小さい、同等、または大きいかにより、ゼロより小さい整数、ゼロ、またはゼロより大きい整数を返す関数を示します。型 caseCompare は値を持つ列挙子です。
exact 大文字と小文字を区別する
ignoreCase 大文字と小文字を区別しない
デフォルト設定は exact で、論理演算子 ==、!= などと同じ結果をもたらします。
言語特定の文字列の照合には、次のメンバ関数を使います。
int RWCString::collate(const RWCString& str) const;
これは、標準 C ライブラリ関数 strcoll() をカプセル化したものです。この関数は、標準 C ライブラリ関数 setlocale() のカテゴリ LC_COLLATE によって設定された言語特有の照合規則によって計算された結果を返します。これは計算量が比較的多いので、次のようにグローバル関数を使って複数の文字列をあらかじめ変形することもできます。
RWCString strXForm(const RWCString&);
そして、結果に compareTo() または ==、!= などの論理演算子のいずれかを使用します。RWCString については、『Tools.h++ Class Reference』を参照してください。
2.11 バージョン
プログラミングを行なう際、ある操作を実行するために Tools.h++ の特定のバージョン番号を調べることができます。この番号は、マクロ RWTOOLS によって 16 進表記で得られます。例えば、バージョン 1.2.3 は、0×123 と表されます。このマクロは、条件付きコンパイルに使用できます。
実行時にバージョン番号が必要な場合は、ヘッダファイル <rw/tooldefs.h> で宣言されている関数 rwToolsVersion() でバージョン番号を知ることができます。
表 2. Tools.h++ クラスの公開クラス階層
これは公開クラスの階層であることに注意してください。クラスの実装は非公開継承を使う場合があります。多重継承のクラスは、階層の両方の場所に示されており、他の基底クラスは右側に斜体で示されています。
RWBench
RWBitVec
RWBTreeOnDisk
RWCacheManager
RWCollectable
RWCollection
RWBag
RWBinaryTree
RWBTree
RWBTreeDictionary
RWHashTable
RWSet
RWFactory
RWHashDictionary
RWIdentityDictionary
RWIdentitySet
RWSequenceable
RWDlistCollectables
RWOrdered
RWSortedVector
RWSlistCollectables
RWSlistCollectablesQueue
RWSlistCollectablesStack
RWCollectableDate (&RWDate)
RWCollectableInt (&RWInteger)
RWCollectableString (&RWCString)
RWCollectableTime (&RWTime)
RWModelClient
RWCRegexp
RWCRExp
RWCString
RWCollectableString (&RWCollectable)
RWCSubString
RWCTokenizer
RWDate
RWCollectableDate (&RWCollectable)
RWErrObject
RWFile
RWFileManager
RWGBitVec(size)
RWGDlist(type)
RWGDlistIterator(type)
RWGOrderedVector(val)
RWGQueue(type)
RWGSlist(type)
RWGSlistIterator(type)
RWGStack(type)
RWGVector(val)
RWGSortedVector(val)
RWInteger
RWCollectableInt (&RWCollectable)
RWIterator
RWBagIterator
RWBinaryTreeIterator
RWDlistCollectablesIterator
RWHashDictionaryIterator
RWHashTableIterator
RWSetIterator
RWOrderedIterator
RWSlistCollectablesIterator
RWLocale
RWLocaleSnapshot
RWMessage
RWModel
RWTime
RWCollectableTime (&RWCollectable)
RWTimer
RWTBitVec<size>
RWTIsvDlist<T>
RWTIsvDlistIterator<TL>
RWTIsvSlist<T>
RWTIsvSlistIterator<TL>
RWTPtrDeque<T>
RWTPtrDlist<T>
RWTPtrDlistIterator<T>
RWTPtrHashMap<Key,Type,Hash,EQ>
RWTPtrHashMapIterator<Key,Type,Hash,EQ>
RWTPtrHashMultiMap<Key,Type,Hash,EQ>
RWTPtrHashMultiMapIterator<Key,Type,Hash,EQ>
RWTPtrHashMultiSet<T,Hash,EQ>
RWTPtrHashMultiSetIterator<T,Hash,EQ>
RWTPtrHashSet<T,Hash,EQ>
RWTPtrHashSetIterator<T,Hash,EQ>
RWTPtrMap<Key,Type,Compare>
RWTPtrMapIterator<Key,Type,Compare>
RWTPtrMultiMap<Key,Type,Compare>
RWTPtrMultiMapIterator<Key,Type,Compare>
RWTPtrMultiSet<T,Compare>
RWTPtrMultiSetIterator<T,Compare>
RWTPtrOrderedVector<T>
RWTPtrSet<T,Compare>
RWTPtrSetIterator<T,Compare>
RWTPtrSlist<T>
RWTPtrSlistIterator<T>
RWTPtrSlistDictionary<KeyP,ValP>
RWTPtrSlistDictionaryIterator<KeyP,ValP>
RWTPtrSortedDlist<T,Compare>
RWTPtrSortedDlistIterator<T,Compare>
RWTPtrSortedVector<T,Compare>
RWTPtrVector<T>
RWTQueue<T,Container>
RWTRegularExpression<charT>
RWTStack<T,Container>
RWTValDeque<T>
RWTValDlist<T>
RWTValDlistIterator<T>
RWTValHashMap<Key,Type,Hash,EQ>
RWTValHashMapIterator<Key,Type,Hash,EQ>
RWTValHashMultiMap<Key,Type,Hash,EQ>
RWTValHashMultiMapIterator<Key,Type,Hash,EQ>
RWTValHashMultiSet<T,Hash,EQ>
RWTValHashMultiSetIterator<T,Hash,EQ>
RWTValHashSet<T,Hash,EQ>
RWTValHashSetIterator<T,Hash,EQ>
RWTValMap<Key,Type,Compare>
RWTValMapIterator<Key,Type,Compare>
RWTValMultiMap<Key,Type,Compare>
RWTValMultiMapIterator<Key,Type,Compare>
RWTValMultiSet<T,Compare>
RWTValMultiSetIterator<T,Compare>
RWTValOrderedVector<T>
RWTValSet<T,C>
RWTValSetIterator<T,C>
RWTValSlist<T>
RWTValSlistIterator<T>
RWTValSlistDictionary<Key,V>
RWTValSlistDictionaryIterator<Key,V>
RWTValSortedDlist<T,Compare>
RWTValSortedDlistIterator<T,Compare>
RWTValSortedVector<T>
RWTValVector<T>
RWTValVirtualArray<T>
RWvios
RWios (virtual) RWvistream
RWbistream (&ios: virtual)
RWeistream
RWpistream
RWXDRistream (&RWios)
RWvostream
RWbostream (&ios: virtual)
RWeostream
RWpostream
RWXDRostream (&RWios)
RWVirtualPageHeap
RWBufferedPageHeap
RWDiskPageHeap
RWWString
RWWSubString
RWWTokenizer
RWZone
RWZoneSimple
streambuf
RWAuditStreamBuffer
RWCLIPstreambuf
RWDDEstreambuf
xmsg
RWxmsg
RWExternalErr
RWFileErr
RWStreamErr
RWInternalErr
RWBoundsErr
RWxalloc