Coherence Extend C++ APIには、堅牢なC++オブジェクト・モデルが収録されています。オブジェクト・モデルはCoherence for C++の基礎となっているので、Coherence APIを使用するうえで、このオブジェクト・モデルを熟知することには大きな利点があります。
Coherence Extend C++ APIには、堅牢なC++オブジェクト・モデルが収録されています。オブジェクト・モデルはCoherence C++の基礎となっています。C++ APIを効果的に使用するには、オブジェクト・モデルを十分に理解する必要があります。
この章は次の項で構成されています。
次の項では、オブジェクト・モデルを使用するコードの記述に関する一般的な情報について説明します。
このcoherenceのネームスペースには、次の汎用ネームスペースがあります。
coherence::lang: オブジェクト・モデルを構成する必須クラス
coherence::util: 各種コレクションなどのユーティリティ・コード
coherence::net: ネットワークおよびキャッシュ
coherence::stl: C++標準テンプレート・ライブラリ統合
coherence::io: シリアライズ
各クラスは専用のヘッダー・ファイルで定義しますが、ネームスペース全体に適用するヘッダー・ファイルを使用すると、関連するクラスを容易に組み込めます。このオブジェクト・モデルを使用するコードには、少なくともcoherence/lang.nsを含めることをお薦めします。
coherence::lang::Objectクラスは、クラス階層のルートです。このクラスは、Coherenceのクラスのインスタンスを抽象化して扱うための共通インタフェースを提供します。オブジェクトは、次の関数をデフォルトで実装できるようにするインスタンス化可能クラスです。
equals
hashCode
clone(オプション)
toStream(Objectをstd::ostreamに書き込みます)
詳細は、C++ APIの「coherence::lang::Object」を参照してください。
Objectクラスには、publicインタフェース以外にも、内部で使用する機能がいくつかあります。これらの機能のうち最も重要なものとして、参照カウンタがあります。このカウンタによって、オブジェクトに割り当てるメモリーを自動的に管理できます。この自動管理機能を使用すると、オブジェクト参照の妥当性とオブジェクト削除の担当範囲に関連付けられる問題の多くがなくなります。また、メモリーのリークや破損を引き起こすプログラミング上のエラーが発生する可能性も低下します。これにより、複雑なシステムを構築するための安定したプラットフォームが得られます。
参照カウンタなど、オブジェクトのライフサイクルに関する情報は、ロックフリーでアトミックなコンペア・アンド・セット操作を使用して、効率的でスレッド・セーフに動作します。これにより、カウントが破損したり別のスレッドの動作によってオブジェクトが予期せずに削除されることもなく、複数のスレッド間でオブジェクトを安全に共有できます。
特定のオブジェクトに対する参照の数を追跡するには、ポインタ割当てとメモリー・マネージャ(この場合はオブジェクト)の間に、あるレベルの協調関係が必要です。基本的には、管理オブジェクトを参照するポインタを設定するたびに、それをメモリー・マネージャに通知する必要があります。通常のC++ポインタを使用した場合は、ポインタを割り当てるたびにメモリー・マネージャへの通知タスクが発生するように、プログラマ側で準備することになります。この作業は煩雑であるばかりでなく、メモリー・マネージャに対するこの通知タスクを準備しておかないと、メモリーのリークや破損を引き起こします。この理由から、スマート・ポインタを使用することで、メモリー・マネージャへの通知タスクをオブジェクト・モデルに置き、このタスクからアプリケーション開発者を解放しています。スマート・ポインタの構文は、通常のC++ポインタに類似していますが、自動的なブックキーピング機能を備えています。
CoherenceのC++オブジェクト・モデルには様々なタイプのスマート・ポインタが用意されています。その主なものは次のとおりです。
View: 参照オブジェクトに対してconstメソッドのみをコールできるスマート・ポインタ。
Handle: 参照オブジェクトに対してconstと非constの両メソッドをコールできるスマート・ポインタ。
Holder: constまたは非constとしてオブジェクトを参照できるようにする特殊なタイプのハンドル。Holderは、オブジェクトが最初にどのように割り当てられたかを保持していて、それと互換性のある形式のみを返します。
View、HandleおよびHolderは最も使用頻度の高いスマート・ポインタです。特別な用途を持つこれ以外のスマート・ポインタについては、この項で後述します。
|
注意: このマニュアルでいうハンドルとは、様々なオブジェクト・モデルのスマート・ポインタを指しています。また、Handleとは、具体的なHandleスマート・ポインタを指します。 |
規則上、各管理クラスには、これらのハンドルに対応するネストしたタイプがあります。たとえば、管理クラスcoherence::lang::Stringでは、String::Handle、String::View、String::Holderを定義しています。
ハンドルの割当ては、通常の継承割当て規則に従います。つまり、HandleをViewに割り当てることはできますが、ViewをHandleに割り当てることはできません。これは、constポインタを非constポインタに割り当てることができないのと同様です。
NULLを参照するハンドルを間接参照すると、従来のセグメンテーション・フォルトはトリガーされず、coherence::lang::NullPointerExceptionがスローされます。
たとえば、次のコードでhs == NULLとすると、NullPointerExceptionがスローされます。
String::Handle hs = getStringFromElsewhere(); cout << "length is " << hs->length() << end1;
すべての管理オブジェクトにはヒープが割り当てられます。オブジェクトをいつ削除できるかは、スタックではなく参照カウントで決まります。スタックに基づく割当てが誤って発生しないように、すべてのコンストラクタは保護され、オブジェクトのインスタンス化では、publicファクトリ・メソッドが使用されます。
このファクトリ・メソッドはcreateという名前で、各コンストラクタにはcreateメソッドが1つずつあります。createメソッドでは、生のポインタでなくHandleが返されます。たとえば、次のコードではStringの新しいインスタンスが作成されます。
String::Handle hs = String::create("hello world");
比較のためにあげた次のコード例は正しくなく、コンパイルできません。
String str("hello world");
String* ps = new String("hello world");
モデル内のオブジェクトはすべて、文字列も含め、Objectで管理され、拡張されます。オブジェクト・モデルでは、char*またはstd::stringを使用するかわりに、専用の管理クラスであるcoherence::lang::Stringを使用します。Stringクラスは、ASCIIおよびすべてのUnicode BMLキャラクタ・セットをサポートしています。
Stringオブジェクトは、char*文字列またはstd::string文字列から容易に構成できます。次にその例を示します。
例2-1 Stringオブジェクトの構成例
const char* pcstr = "hello world"; std:string stdstr(pcstr); String::Handle hs = String::create(pcstr); String::Handle hs2 = String::create(stdstr);
管理文字列は、指定した文字列のコピーですが、元の文字列に対する参照もポインタも含まれていません。getCString()メソッドを使用すると、管理文字列を変換して他の任意の型の文字列に戻すことができます。このメソッドでは、元のconst char*へのポインタが返されます。COH_TO_STRINGマクロを組み合せると、C++の標準的な演算子<<を使用してStringを作成することもできます。
引用符を使用した文字列リテラルを使用しやすくするために、String::HandleおよびString::Viewではconst char*およびconst std::stringでの自動ボックス付け処理をサポートしています。これにより、前述のサンプルのコードを次のように記述できます。
自動ボックス付け処理は、他の型でも可能です。詳細は、「coherence::lang::BoxHandle」を参照してください。
Handleは型保証であり、次の例ではStringではないObjectが存在するため、コンパイラでObject::HandleをString::Handleに割り当てることはできません。
Object::Handle ho = getObjectFromSomewhere(); String::Handel hs = ho; // will not compile
ただし、次の例ではすべてのStringがObjectであるため、コンパイルが可能です。
例2-4 型保証キャストの例
String::Handle hs = String::create("hello world");
Object::Handle ho = hs; // will compile
派生したObject型にダウン・キャストする場合は、C++のRTTI(実行時型情報)を使用して動的キャストを実行し、そのキャストが有効であるようにする必要があります。オブジェクト・モデルには、構文を容易にするヘルパー関数が用意されています。
cast<H>(o): 指定されたハンドルoを型Hに変換します。失敗した場合はClassCastExceptionをスローします。
instanceof<H>(o): oからHへのキャストが可能かどうかをテストします。可能な場合はtrue、不可能な場合はfalseをそれぞれ返します。
これらの関数は標準的なC++のdynamic_cast<T>に似ていますが、生のポインタへのアクセスを必要としません。
次の例は、Object::HandleからString::Handleにダウン・キャストする方法を示しています。
例2-5 ダウン・キャストの例
Object::Handle ho = getObjectFromSomewhere(); String::Handle hs = cast<String::Handle>(ho);
指定されたオブジェクトが予期された型ではない場合、cast<H>関数はcoherence::lang::ClassCastExceptionをスローします。instanceof<H>関数を使用すると、例外がスローされることなく、オブジェクトが特定の型であるかどうかをテストできます。このようなチェックは一般的に、実際の型に疑問がある場合にのみ必要となります。
マネージ配列は、coherence::lang::Array<T>テンプレート・クラスを使用して得ることができます。このクラスは管理対象であり、安全な自動メモリー管理機能を追加するほか、配列全体の長さを含み、チェック済の索引付けをバインドします。
次の例のように、配列のHandleの添字演算子を使用して、その配列に索引を付けることができます。
例2-7 配列の索引付け
Array<int32_t>::Handle harr = Array<int32_t>::create(10);
int32_t nTotal = 0;
for (size32_t i = 0, c = harr->length; i < c; ++i)
{
nTotal += harr[i];
}
オブジェクト・モデルは、C++のプリミティブと管理されているObjectの配列をサポートしています。サポートされているのはObjectの配列のみで、派生したObject型の配列は対象外です。派生したハンドルの型を取得するには、キャストを採用する必要があります。Objectの配列は、技術的にはArray<MemberHolder<Object> >となりますが、わかりやすくするためにtypedefでObjectArrayとして宣言します。
coherence::util*ネームスペースには、コレクション・クラスとインタフェースがいくつかあり、これらをアプリケーションで使用すると便利です。これらには、次のものが含まれます。
coherence::util::Collection: インタフェース
coherence::util::List: インタフェース
coherence::util::Set: インタフェース
coherence::util::Queue: インタフェース
coherence::util::Map: インタフェース
これらのクラスは、Coherence Extend APIの一部にもなっています。
ObjectArrayと同様、CollectionにはObject::Holderが含まれているため、あらゆるタイプの管理オブジェクトのインスタンスを格納できます。
オブジェクト・モデルでは、例外も管理オブジェクトです。これにより、オブジェクト・スライシングを発生させないで、捕捉した例外をローカル変数またはデータ・メンバーとして保持できます。
Coherenceの例外はすべて、throwable_specを使用して定義し、coherence::lang::Exceptionクラスから派生します。また、このクラスはObjectから派生します。マネージ例外は、C++の標準的なthrow文を使用して明示的にスローされるのではなく、COH_THROWマクロを使用してスローされます。このマクロはスタック情報を設定してから、例外のraiseメソッドをコールします。このメソッドが最終的にスローをコールします。これによってスローされたオブジェクトは、対応する例外のView型または継承されたView型として捕捉できます。さらに、これらのマネージ例外は、標準的なconst std::exceptionクラスとして捕捉できます。次の例は、マネージ例外を使用したtry/catchブロックを示しています。
例2-9 マネージ例外を使用したtry/catchブロック
try
{
Object::Handle h = NULL;
h->hashCode(); // trigger an exception
}
catch (NullPointerException::View e)
{
cerr << "caught" << e <<endl;
COH_THROW(e); // rethrow
}
|
注意: この例外は、Exception::Viewまたはconst std::exception&として捕捉することもできます。 |
C++では、オブジェクトがどのように宣言されたか(たとえばconstとして宣言されたオブジェクト)という情報は、オブジェクトへのポインタや参照からは得られません。たとえば、const Foo*型のポインタでは、そのポインタのユーザーにはオブジェクトの状態を変えることができないという点が示されるのみです。参照先のオブジェクトが実際にconstとして宣言されたものかどうか、また変化しないことが確実なのかどうかはわかりません。オブジェクト・モデルでは、実行時の不変性機能を追加し、状態が変化しないオブジェクトを識別できるようにしています。
Objectクラスには、Handle用とView用の2つの参照カウンタが保持されています。Viewのみから参照されているオブジェクトは、定義上は不変です。これは、Viewではオブジェクトの状態を変えることができず、ViewからHandleを取得することもできないためです。(Objectクラスに含まれる)isImmutable()メソッドで、この状態をテストできます。これは仮想メソッドであり、不変の定義をサブクラスで変更できるようにします。たとえば、Stringには非constメソッドがないため、そのisImmutable()メソッドは必ずtrueを返します。
不変になったオブジェクトを可変に戻すことはできません。constの性質を捨ててViewをHandleに変えることは、確認済の不変性に違反するため不可能です。
キャッシングの観点から不変性は重要です。CoherenceのNearCacheおよびContinuousQueryCacheでは、不変性を利用することで、オブジェクトの直接参照をキャッシュに格納できるかどうか、または参照のコピーを作成する必要があるかどうかを判断できます。さらに、オブジェクトが変更不可能であることがわかれば、同期化を行わないで、マルチスレッドによる安全な対話処理が可能になります。
オブジェクト・モデルに既存のクラスを統合することが必要になる場面が多く見られます。その典型的な例として、管理オブジェクトの格納のみをサポートしているCoherenceキャッシュにデータ・オブジェクトを格納する必要がある状況があげられます。以前から存在しているクラスに対してcoherence::lang::Objectの拡張への変更を要求することは合理的ではないため、オブジェクト・モデルにはアダプタが用意されています。このアダプタは、非管理の単純な古いC++クラスのインスタンスを実行時に管理クラスのインスタンスに自動的に変換します。
これは、coherence::lang::Managed<T>テンプレート・クラスを使用して実現できます。このテンプレート・クラスは、Objectおよび指定のテンプレート・パラメータ型Tを拡張したもので、実質的にObjectでもありTでもある新しいクラスが生成されます。この新しいクラスはTから初期化できます。また、変換してTに戻すこともできます。これによって得られる機能は、使用方法は簡単ですが、管理コードと非管理コードを結び付ける高度な性能を備えています。
詳細と例は、coherence::lang::Managedに関するAPIドキュメントを参照してください。
次の項では、Objectを拡張したクラスである新規管理クラスの作成に必要な情報について説明します。新規管理クラスの作成は、EventListener、EntryProcessor、Filterのいずれかの型を新規に作成する場合に必要となります。既存のC++データ・オブジェクトを扱う場合やCoherenceのC++ APIを利用する場合は、管理クラスを新規に作成する必要はありません。オブジェクト・モデルへの非管理クラスの統合の詳細は、前の項を参照してください。
仕様に基づく定義(spec)を使用すると、C++で管理クラスを迅速に定義できます。
管理クラスの独自の実装を作成するときは、仕様に基づく定義が役に立ちます。
次のように、様々なタイプのクラスの作成に使用する数多くの形式のspecがあります。
class_spec: 標準的なインスタンス化可能なクラスの定義
cloneable_spec: クローニング可能なクラスの定義
abstract_spec: 真の仮想メソッドをゼロ個以上持つ、インスタンス化不可能なクラスの定義
interface_spec: インタフェース定義(真に仮想で多重継承可能なクラス)
throwable_spec: 例外としてスロー可能な管理クラス
specでは、specの対象となるクラスに対して次の機能が自動的に定義されます。
Handle、View、Holder
保護されたコンストラクタに委任される静的なcreate()メソッド
コピー・コンストラクタに委任される仮想clone()メソッド
::sizeof()に基づく仮想sizeOf()
定義したクラスの派生元クラスを参照するためのスーパーtypedef
coherence::lang::Objectからの継承(extends<>を使用して親クラスが指定されていない場合)
specを使用してクラスを定義するには、前述のspecのいずれかからそのクラスをパブリックに継承します。これらのspecはそれぞれ、パラメータ化されたテンプレートです。このパラメータは次のとおりです。
定義するクラスの名前。
パブリックに継承する元のクラスで、extends<>文を使用して指定します。この指定は、デフォルトではextends<Object>です。
interface_specには、この要素はありません。
extends<Object>を指定した場合を除き、親クラスは仮想的には派生しません。
このクラスで実装するインタフェースのリスト。implements<>文で指定します。
すべてのインタフェースは、パブリックな仮想継承を使用して派生します。
extends<>パラメータは、インタフェースの定義には使用しません。
例2-10は、interface_specを使用してComparableインタフェースを定義する方法を示しています。
例2-10 interface_specで定義するインタフェース
class Comparable
: public interface_spec<Comparable>
{
public:
virtual int32_t compareTo(Object::View v) const = 0;
};
例2-11は、interface_specを使用して派生インタフェースNumberを定義する方法を示しています。
例2-11 interface_specで定義する派生インタフェース
class Number
: public interface_spec<Number,
implements<Comparable> >
{
public:
virtual int32_t getValue() const = 0;
};
次に、cloneable_specを使用して実装を生成します。これを例2-12に示します。
|
注意: 自動生成のcreateメソッドをサポートするには、インスタンス化可能なクラスでcoherence::lang::factory<>テンプレートをフレンドとして宣言する必要があります。規則上は、これがクラス本体の先頭に記述する文となります。 |
例2-12 cloneable_specで定義する実装
class Integer
: public cloneable_spec<Integer,
extends<Object>,
implements<Number> >
{
friend class factory<Integer>;
protected:
Integer(int32_t n)
: super(), m_n(n)
{
}
Integer(const Integer& that)
: super(that), m_n(that.m_n)
{
}
public:
virtual int32_t getValue() const
{
return m_n;
}
virtual int32_t compareTo(Object::View v) const
{
return getValue() - cast<Integer::View>(v)->getValue();
}
virtual void toStream(std::ostream& out) const
{
out << getValue();
}
private:
int32_t m_n;
};
例2-12のクラス定義は、例2-13に示す、仕様に基づかないクラス定義と同等です。
例2-13 specを使用しないクラスの定義
class Integer
: public virtual Object, public virtual Number
{
public:
typedef TypedHandle<const Integer> View; // was auto-generated
typedef TypedHandle<Integer> Handle; // was auto-generated
typedef TypedHolder<Integer> Holder; // was auto-generated
typedef super Object; // was auto-generated
// was auto-generated
static Integer::Handle create(const int32_t& n)
{
return new Integer(n);
}
protected:
Integer(int32_t n)
: super(), m_n(n)
{
}
Integer(const Integer& that)
: super(that), m_n(that.n)
{
}
public:
virtual int32_t getValue() const
{
return m_n;
}
virtual int32_t compareTo(Object::View v) const
{
return getValue() - cast<Integer::View>(v)->getValue();
}
virtual void toStream(std::ostream& out) const
{
out << getValue();
}
// was auto-generated
virtual Object::Handle clone() const
{
return new Integer(*this);
}
// was auto-generated
virtual size32_t sizeOf() const
{
return ::sizeof(Integer);
}
private:
int32_t m_n;
};
例2-14は、specの対象となるクラスを使用して定義する方法を示しています。
このタイトルにあげた概念の共通項を考えてみてください。これらはすべてオブジェクトの状態を示すもので、一般的に実装面で似たような問題点を抱えています。簡単に説明すると、前述のメソッドのいずれかで参照しているすべてのデータ・メンバーは、すべてのメソッドで参照することが必要になる可能性があります。逆に、いずれかのメソッドで参照していないデータ・メンバーは、どのメソッドでも参照できなくなる可能性があります。ここでは簡単な例として、HashSet::Entryに既知のキーと値のデータ・メンバーがあるとします。これらのデータ・メンバーは、必ずequalsメソッドで考慮することになっています。データ・メンバーの等価性については、参照の等価性ではなく、それぞれが持つequalsメソッドへのコールを使用してテストします。このEntryが、HashSetの実装の一部として、HashSetのバケットの中で次の順番にあるEntryへのハンドルを持っていて、同時にHashSet自身へのハンドルも持っているとします。この場合のデータ・メンバーもequalsで考慮する必要があるかどうかを検討します。この場合、その必然性はないと考えられます。等価なキーと値を持ち、別々の2つのマップにある2つのエントリを比較すると、その結果は等価になると考えることが合理的です。この考え方に従うと、Entryに対するhashCodeメソッドでは、キーと値以外のデータ・メンバーが完全に無視され、EntryのhashCodeは、データ・メンバーのIDのhashCodeではなく、そのキーと値のhashCodeの結果を使用して計算されます。つまり、equalsによる詳細な等価性チェックでは、hashCodeによって詳細なハッシュが生成されることを意味します。クローニングについて考えると、Entryのクローニングでは、キーと値以外のデータ・メンバーのクローニングは不要になると考えられます。Entryのクローニングの過程で親Mapをクローニングすることには明らかに意味がなく、次の順番にあるEntryへのハンドルのクローニングにも同様のことがいえます。この考え方は、isImmutableメソッドとシリアライズにも同様に適用できます。必ずしも必須で厳密な規則としての考え方ではありませんが、前述のメソッドのいずれかを実装する際には検討の余地があります。
オブジェクト・モデルにはマネージ・スレッドが用意されており、これを使用すると、プラットフォームに依存しないマルチスレッドのアプリケーションを容易に作成できます。スレッド処理の抽象化では、スレッドの作成、割込みおよび結合のサポートを扱います。スレッド・ローカル記憶域は、coherence::lang::ThreadLocalreferenceクラスで利用できます。診断とトラブルシューティングのためにスレッドのダンプを利用することも可能です。究極的にはマネージ・スレッドとは、POSIXやWindowsのスレッドなど、システムがネイティブで持つタイプのスレッドのラッパーです。このスレッド処理の抽象化はCoherenceで内部的に使用されていますが、必要に応じてアプリケーションでも利用できます。
例2-15は、Runnableインスタンスを新規作成し、スレッドを生成する方法を示しています。
例2-15 Runnableインスタンスの作成とスレッドの生成
class HelloRunner
: public class_spec<HelloRunner,
extends<Object>,
implements<Runnable> >
{
friend class factory<HelloRunner>;
protected:
HelloRunner(int cReps)
: super(), m_cReps(cReps)
{
}
public:
virtual void run()
{
for (int i = 0; i < m_Reps; ++i)
{
Thread::sleep(1000);
std::cout << "hello world" << std::endl;
}
}
protected:
int m_cReps;
};
...
Thread::Handle hThread = Thread::create(HelloRunner::create(10));
hThread->start();
hThread->join();
詳細は、「coherence::lang::Thread」および「coherence::lang::Runnable」を参照してください。
参照カウント・スキームの機能上の大きな制限として、循環オブジェクト・グラフの自動クリーンアップがあります。図2-1に示す簡単な双方向の関係について考えます。
この図では、AとBの両方が、それぞれをアクティブに維持する参照カウントを1つ持っています。AとBは、互いをアクティブに維持しているものがAとB自身のみであること、また外部からAとBを参照しているものが存在しないことを認識していません。参照カウントのみではこの自立型グラフを扱うことができず、メモリー・リークが発生します。
グラフを扱うために用意されているメカニズムが弱参照です。弱参照はオブジェクト参照の一種ですが、そのオブジェクトの削除が可能です。図2-2に示すように、次のように変更することによって、A->B->Aという問題を解決できます。
ここでは、AがBに対する弱参照を持っています。Bに対する参照が弱参照のみになると、Bは自身に対する弱参照をすべてクリアしてから削除されます。この簡単な例では、BはAに対する参照のみを保持していたため、Aの削除もトリガーされます。
弱参照を使用すると、この例よりも複雑な構成が可能になります。ただし、どの参照が弱参照でどの参照が強参照かを規定する規則の採用が必要になります。図2-3に示すツリーを考えてみます。このツリーはA、B、Cの各ノードで構成され、このツリーに対する2つの外部参照XおよびYが存在します。
このツリーでは、親(A)が子(BとC)に対して強参照を使用し、子は親に対して弱参照を使用しています。この図の状態であれば、参照Yは、子Bを起点としてツリー上をAに登ってからCに降りるという方法で、ツリー全体をナビゲートできます。ここで、参照XがNULLにリセットされた場合を考えてみます。この場合は、Aが弱参照のみされた状態で残り、自身に対する弱参照はすべてクリアされてから削除されます。Aが削除されると、Cに対する参照がまったく存在しなくなるので、Cも削除されます。この時点で、それまで何のアクションも起こしていない参照Yは、図2-4に示す状態を参照することになります。
これが必ず問題となるわけではありませんが、弱参照の使用では考慮を必要とする状況です。この問題を回避するには、YのホルダーでもAに対する参照を保持することが考えられます。これにより、ツリーが予期せずに解消されないようにします。
使用方法の詳細は、coherence::lang::WeakReference、WeakHandleおよびWeakViewに関するJavadocを参照してください。
C++では知られていることですが、作成中のオブジェクトを参照することは危険です。特にコンストラクタではこのような参照は避けてください。それは、コンストラクタの内部ではオブジェクトの初期化が完了していないためです。管理オブジェクトの場合、作成中のオブジェクトに対するコンストラクタからのハンドルを作成すると、ほとんどの場合、オブジェクトは作成が完了しないうちに破棄されます。この問題に対処するために、オブジェクト・モデルでは仮想コンストラクタがサポートされています。仮想コンストラクタonInitはObjectによって定義し、派生クラスでオーバーライドできます。このメソッドは、作成の完了直後、オブジェクト・モデルの静的createメソッドから新しいオブジェクトが返される直前に、オブジェクト・モデルから自動的にコールされます。onInitメソッドの内部では、作成中のオブジェクトの参照、仮想関数のコール、他のクラスのインスタンスへの新規オブジェクト参照の移譲などを実行しても安全です。onInitから派生したあらゆる実装では、super::onInit()のコールを指定して、親クラスで自身の初期化もできるようにする必要があります。
すでに説明したHandleとViewのスマート・ポインタのほかに、オブジェクト・モデルには、使用できる専用のバリアントがいくつかあります。ほとんどの場合、これらの専用のスマート・ポインタは、その用途が管理クラスの作成に限定されているため、通常のアプリケーションのコードでは使用されません。
表2-1 Coherence for C++でサポートされている高度なハンドルのタイプ
| タイプ | スレッド・セーフ対応 | View | 注意 |
|---|---|---|---|
|
なし |
Tによって異なる |
|
|
|
なし |
Tによって異なる |
プリミティブ・タイプから管理オブジェクトを自動的に作成できます。 |
|
|
なし |
可能性あり |
|
|
|
なし |
あり |
参照しているオブジェクトが |
|
|
あり |
なし |
参照しているオブジェクトの破棄を防止しません。 |
|
|
あり |
あり |
参照しているオブジェクトの破棄を防止しません。 |
|
|
coherence:lang:WeakHolder<T> |
あり |
あり |
参照しているオブジェクトの破棄を防止しません。 |
|
あり |
なし |
包含オブジェクトが持つ |
|
|
あり |
あり |
スレッド・セーフな |
|
|
あり |
可能性あり |
スレッド・セーフな |
|
|
coherence:lang:FinalHandle<T> |
あり |
なし |
読取り専用の |
|
coherence:lang:FinalView<T> |
あり |
あり |
スレッド・セーフな読取り専用 |
|
coherence:lang:FinalHolder<T> |
あり |
可能性あり |
スレッド・セーフな読取り専用の |
ベースのObjectクラスがスレッド・セーフであっても、その派生クラスの状態は自動的にはスレッド・セーフになりません。より高度なスレッド・セーフティを個々の派生クラスで実現できるかどうかは、個々のクラスの実装にかかっています。オブジェクト・モデルには、スレッド・セーフなコードの作成を支援する各種機能があります。
オブジェクト・モデルにあるどのObjectも、同期と通知のポイントとすることができます。オブジェクトを同期し、その内部モニターを取得するには、例2-16に示すように、COH_SYNCHRONIZEDマクロ・コード・ブロックを使用します。
例2-16 COH_SYNCHRONIZEDマクロ・コード・ブロックのサンプル
SomeClass::Handle h = getObjectFromSomewhere();
COH_SYNCHRONIZED (h)
{
// monitor of Object referenced by h has been acquired
if (h->checkSomeState())
{
h->actOnThatState();
}
} // monitor is automatically released
このCOH_SYNCHRONIZEDブロックでは、モニターの取得と解放が実行されます。return、throw、COH_THROW、break、continue、gotoの各文を使用して、このブロックを安全に終了できます。
Objectクラスには、通知に使用するwait()、wait(timed)、notify()、notifyAll()の各メソッドがあります。これらのメソッドをコールするには、目的のObjectのモニターをコール元で取得しておく必要があります。詳細は、「coherence::lang::Object」を参照してください。
読取り/書込みロックも用意されています。詳細は、「coherence::util::ThreadGate」を参照してください。
管理クラスに対して定義されているHandle、ViewおよびHolderのネストされた型は、意図的にスレッド・セーフになっていません。したがって、1つのハンドルを複数のスレッドで共有することは安全ではありません。ここで説明するスレッド・セーフティはハンドルに関するものであり、ハンドルで参照するオブジェクトに関するものではありません。この点は明確に区別しておくことが重要です。同期処理を追加しなくても、それぞれ異なるスレッドで複数の独立したハンドルから1つの同じオブジェクトを参照すれば安全です。
これらのハンドル・タイプがスレッド・セーフティを備えていないことは、パフォーマンスの最適化の面で大きな利点となります。ハンドルの大半にはスタックが割り当てられるからです。これらのスタック割当てハンドルへの参照を複数のスレッドで共有しないかぎり、スレッド・セーフティに関する問題が発生することはありません。
1つのハンドルが複数のスレッドで共有される可能性がある場合は、スレッド・セーフなハンドルが必要になります。次はその典型的な例です。
グローバル・ハンドル: 標準のハンドル・タイプをグローバル変数や静的変数として使用することは安全ではありません。
管理されていない複数スレッド・アプリケーション・コード: 複数のスレッドで共有される可能性のあるデータ構造内で標準のハンドルを使用することは安全ではありません。
ハンドルをデータ・メンバーとして持つ管理クラス: 管理クラスのどのインスタンスであっても、複数のスレッドで共有される可能性を想定しておく必要があります。したがって、標準のハンドルをデータ・メンバーとして使用することは安全ではありません。すべての管理クラスが必ずしも複数のスレッドで共有されるわけではありませんが、明示的に管理できないコードにインスタンスを渡した場合(キャッシュに置いた場合など)、そのオブジェクトが他のスレッドからは隠蔽されているとはかぎりません。
このような場合は、標準ハンドルではなく、スレッド・セーフなハンドルを使用する必要があります。オブジェクト・モデルには、次のスレッド・セーフなハンドルが用意されています。
coherence::lang::MemberHandle<T>: T::Handleのスレッド・セーフ・バージョン
coherence::lang::MemberView<T>: T::Viewのスレッド・セーフ・バージョン
coherence::lang::MemberHolder<T>: T::Holderのスレッド・セーフ・バージョン
coherence::lang::FinalHandle<T>: T::Handleのスレッド・セーフ・バージョン
coherence::lang::FinalView<T>: T::Viewのスレッド・セーフ・バージョン
coherence::lang::FinalHolder<T>: T::Holderのスレッド・セーフ・バージョン
coherence::lang::WeakHandle<T>: Tへのスレッド・セーフな弱参照ハンドル
coherence::lang::WeakView<T>: Tへのスレッド・セーフな弱参照ビュー
coherence::lang::WeakHolder<T>: スレッド・セーフな弱参照T::Holder
これらのハンドル・タイプは、同期処理を追加しなくても、複数のスレッドからの読取りと書込みが可能です。これらは主に他の管理クラスのデータ・メンバーとして使用することを目的としたもので、各インスタンスにはガーディアン管理Objectへの参照を指定します。ガーディアンのスレッド・セーフな内部アトミック状態を使用して、ハンドルのスレッド・セーフティを実現します。これらのハンドル・タイプを使用するとき、1つのコード・ブロックの中でそのハンドルへのアクセスが複数回発生する場合には、通常のスタックに基づくハンドルに読み取ることをお薦めします。標準のスタックに基づくハンドルへのこの割当てはスレッド・セーフであり、割当てが完了すると、そのスタックに基づくハンドルを基本的に自由に間接参照できるようになります。スレッド・セーフ・ハンドルを初期化するときは、ガーディアンObjectへの参照を1番目のパラメータとして指定する必要があります。この参照は、包含オブジェクトに対してself()をコールすることで得られます。
この簡単な例を例2-17に示します。
例2-17 スレッド・セーフ・ハンドル
class Employee
: public class_spec<Employee>
{
friend class factory<Employee>;
protected:
Employee(String::View vsName, int32_t nId)
: super(), m_vsName(self(), vsName), m_nId(nId)
{
}
public:
String::View getName() const
{
return m_vsName; // read is automatically thread-safe
}
void setName(String::View vsName)
{
m_vsName = vsName; // write is automatically thread-safe
}
int32_t getId() const
{
return m_nId;
}
private:
MemberView<String> m_vsName;
const int32_t m_nId;
};
この同じ基本的な手法は、非管理クラスにも適用できます。非管理クラスはcoherence::lang::Objectを拡張したものではないので、スレッド・セーフなハンドルのガーディアンとしては使用できません。ただし、これ以外のObjectをガーディアンとして使用することは可能です。この方法を使用する場合、ガーディアンObjectの存続時間を、保護対象のスレッド・セーフなハンドルの存続時間よりも長くなるよう設定する必要があります。Coherence 3.4.1以降でこの処理を簡素化するには、System::common()をコールしてcoherence::lang::Systemからランダムな永続的ガーディアンを取得します。これを例2-18に示します。
例2-18 非管理クラスとしてのスレッド・セーフ・ハンドル
class Employee
{
public:
Employee(String::View vsName, int32_t nId)
: m_vsName(System::common(), vsName), m_nId(nId)
{
}
public:
String::View getName() const
{
return m_vsName;
}
void setName(String::View vsName)
{
m_vsName = vsName;
}
int32_t getId() const
{
return m_nId;
}
private:
MemberView<String> m_vsName;
const int32_t m_nId;
};
管理クラスを作成する際に、self()、System::common()の順にコールしてガーディアンを取得することをお薦めします。
|
注意: まれな状況として、mutableキーワードを使用してこれらのハンドルのいずれかを宣言している場合は、コンストラクションの際にfMutableをtrueに設定してそのことを通知する必要があります。 |
スレッド・セーフなハンドルは、グローバル・ハンドルなどと同様、クラス共有ではないデータで使用できます。
MemberView<NamedCache> MY_CACHE(System::common());
int main(int argc, char** argv)
{
MY_CACHE = CacheFactory::getCache(argv[0]);
}
オブジェクト・モデルは、エスケープ分析に基づく最適化の機能を備えています。エスケープ分析は、管理オブジェクトを認識できるスレッドが1つのみである状態を自動的に特定し、特定した場合には不要な同期を排除した最適化を実行するために使用します。非エスケープ・オブジェクトに対して、次の操作タイプを最適化できます。
参照カウントの更新
COH_SYNCHRONIZEDの取得と解放
スレッド・セーフなハンドルの読取りと書込み
不変オブジェクトからのスレッド・セーフなハンドルの読取り
エスケープ分析は自動的に実行されますが、オブジェクト・モデルの使用ルールに従っているかぎり安全です。取得したスレッド・セーフなハンドルを使用せずに複数のスレッド間で管理オブジェクトを渡すのは、明らかに安全ではない操作です。外部の機能を使用して管理オブジェクトを渡すと、エスケープ分析でエスケープ状態を特定できないため、メモリー破損やその他のランタイム・エラーの原因となります。
管理クラス・タイプごとに、Handle、ViewおよびHolderのネストした定義があります。これらのハンドルは、Coherence APIやアプリケーション・コードで広く使用されています。これらは、管理オブジェクトへのスタックに基づく参照として使用することを目的としたものです。複数のスレッドへの公開は意図されていません。つまり、複数のスレッドで1つのハンドルを共有しないようにする必要があります。ただし、複数のスレッドから管理オブジェクトを参照することは、独立したHandleを使用するか、スレッド・セーフなMemberHandle、MemberViewまたはMemberHolderを使用しているかぎり安全です。
重要な点として、管理オブジェクトへのグローバル・ハンドルは共有される可能性を除外できないため、前述のようにスレッド・セーフにしておく必要があります。グローバル・ハンドルをスレッド・セーフにしておかない場合、エスケープ・オブジェクトが適切に識別されないため、メモリーの破損が発生する可能性があります。
リリース3.4では、このようなスレッド・セーフではないハンドルであっても、外部の同期機能を使用するか、そのハンドルが読取り専用であれば、複数のスレッドで共有できました。リリース3.5以降では、この機能はサポートされていません。これらのハンドルは、読取り専用モードや外部の同期機能で使用しても、スレッド・セーフではありません。これは、1つのハンドルを別のハンドルに割り当てるという、日常的に発生する操作にかかるコストを大幅に削減するために、実装が根本的に変更されたためです。リリース3.4の方式でハンドルを使用するコードは、スレッド・セーフなハンドルを使用するように更新する必要があります。詳細は、「スレッド・セーフなハンドル」を参照してください。
Coherenceの様々な機能の中でも特にエスケープ分析では、オブジェクトについて計算した不変性を活用し、データ・メンバーの状態変更がこれからも起こり得るかどうかを判断します。つまり、オブジェクトが単にViewから参照されているだけであれば、そのデータ・メンバーにはこれ以上の更新が発生しないと見なされます。C++言語には、このconstのみに対するアクセスをバイパスし、constメソッドからのミューテーションを可能にする機能がいくつか用意されています。データ・メンバーの宣言での可変キーワードの使用やキャストによるconst性の破棄などがこの例です。より最適な解決策であると同時に、オブジェクト・モデルでサポートされている手法は可変キーワードです。Coherenceのオブジェクト・モデルでは、スレッド・セーフなデータ・メンバーのハンドルを可変として宣言した場合は、その情報をそのデータ・メンバーに通知する必要があります。スレッド・セーフなすべてのデータ・メンバーは、オプションの3番目のパラメータとしてfMutableをサポートしています。可変キーワードを使用してデータ・メンバーを宣言している場合は、このパラメータをtrueに設定する必要があります。これにより、データ・メンバーの包含オブジェクトがViewから参照されているだけであっても、そのデータ・メンバーをconstとは見なさないようにエスケープ分析ルーチンに通知できます。キャストによって管理オブジェクトのconst性を破棄する方法はサポートされていません。また、オブジェクトの状態の変更が発生しないという判断がオブジェクト・モデルでなされている場合、この方法ではランタイム・エラーが発生することがあります。
Coherence for C++は、APIで多用される動的割当てのパフォーマンス向上のためにスレッドローカル・アロケータを備えています。デフォルトでは、スレッドごとに最大64KBの再利用可能メモリー・ブロックのプールがあり、このプールは、動的なオブジェクト割当てのほとんどに対応しています。次のシステム・プロパティを使用すると、このプールを構成できます。
tangosol.coherence.slot.size: プールからの割当ての対象と見なすオブジェクトの最大サイズを制御します。デフォルト値は128バイトです。このサイズを超えるオブジェクトの場合は、システムのmallocルーチンをコールして所要のメモリーを取得します。
tangosol.coherence.slot.count: 各スレッドにおける割当てを処理するスロットの数を制御します。デフォルト値は512スロットです。利用できるスロットが存在しない場合、割当てはmallocにフォール・バックします。
tangosol.coherence.slot.refill: プールへの補充を促すトリガーをスロットで取得しない比率を制御します。デフォルト値である10,000は、10,000のプールのうちの1つにはプールの補充に使用できる割当てを適用しないことを示しています。
プールに対するこのアロケータは、そのサイズまたは個数を0に設定することで無効にできます。
この項では、オブジェクト・モデルを利用するアプリケーションで発生した問題の診断に役立つ情報について説明します。
診断とトラブルシューティングのためにスレッドのダンプを利用することが可能です。このスレッドのダンプにはスタック・トレースも含まれます。スレッドのダンプを生成するには、Windowsの場合は[Ctrl]キーを押しながら[Break]キーを押し、UNIXの場合は[Ctrl]キーを押しながら[\]キーを押します。例2-19は、スレッドのダンプのサンプルを示しています。
例2-19 スレッドのダンプのサンプル
Thread dump Oracle Coherence for C++ v3.4b397 (Pre-release) (Apple Mac OS X x86 debug) pid=0xf853; spanning 190ms
"main" tid=0x101790 runnable: <native>
at coherence::lang::Object::wait(long long) const
at coherence::lang::Thread::dumpStacks(std::ostream&, long long)
at main
at start
"coherence::util::logging::Logger" tid=0x127eb0 runnable: Daemon{State=DAEMON_RUNNING, Notification=false,
StartTimeStamp=1216390067197, WaitTime=0, ThreadName=coherence::util::logging::Logger}
at coherence::lang::Object::wait(long long) const
at coherence::component::util::Daemon::onWait()
at coherence::component::util::Daemon::run()
at coherence::lang::Thread::run()
管理されているオブジェクト・モデルの参照カウントはメモリー・リークの防止に効果的ですが、それでもメモリー・リークが発生する可能性はあります。この発生要因としてよく見られるものに、循環オブジェクト・グラフがあります。オブジェクト・モデルではヒープ分析がサポートされています。これは、システムに存在するアクティブなオブジェクトの数を追跡することで、リークが発生しているかどうかの識別を容易にする機能です。これは、時間の経過に応じてオブジェクトの数を比較し、その数が一貫して増加しているかどうかを検出する簡単な方法です。この数が増加していると、リークが発生している可能性があります。リークの可能性が検出された場合は、その発生源がどの型のオブジェクトであるかについて得られた統計により、ヒープ・アナライザを使用してリークを追跡できます。
Coherenceには、プラッガブルなcoherence::lang::HeapAnalyzerインタフェースが用意されています。HeapAnalyzerの実装は、tangosol.coherence.heap.analyzerシステム・プロパティを使用して指定できます。このプロパティは、次のいずれかの値に設定できます。
none: ヒープ分析を実行しません。これがデフォルトの設定です。
object: coherence::lang::ObjectCountHeapAnalyzerを使用します。この指定では、システムに存在するアクティブなオブジェクトの数のみに基づいて、簡単なヒープ分析を実行します。
class: coherence::lang::ClassBasedHeapAnalyzerを使用します。この指定では、クラス・レベルでのヒープ分析を実行します。つまり、アクティブなオブジェクトの数とそれに伴うバイト・レベルの使用量を、クラスごとに追跡します。
alloc: coherence::lang::ClassBasedHeapAnalyzerを特殊化したもので、クラス・レベルの割当て数を追加で追跡します。
custom: 独自の分析ルーチンを定義できます。SystemClassLoaderに登録済のクラスの名前を指定します。
Windowsでは[Ctrl]キーを押しながら[Break]キーを押し、UNIXでは[Ctrl]キーを押しながら[\]キーを押すと、ヒープ情報が返されます。
例2-20は、クラス・ベースのアナライザで返されたヒープ分析情報の例です。ここでは、Mapに新しいエントリを挿入したことで得られたヒープ分析の差分が返されます。
メモリー破損を防止するためにオブジェクト・モデルで実行する機能はすべて、破損を引き起こす可能性のある非管理コードで使用することが普通です。このために、オブジェクト・モデルではメモリー破損の検出がサポートされています。メモリー破損の検出が有効になっていると、オブジェクト・モデルのメモリー・アロケータによって、構成可能なパッド・バイト数で、各オブジェクトへの割当ての先頭と末尾がパディングされます。このパディングは、パッドが変更されていないことを後で検証できるようなパターンでエンコードされています。メモリー破損が発生し、パッドのいずれかがその影響を受けると、以降の検証でこの破損が検出されます。検証は、オブジェクトが破棄されたときに実行されます。
Coherence C++ APIのデバッグ・バージョンでは、2×ワード・サイズの大きさを持つパッドをオブジェクトへの割当ての両側に置くパディング機能が、デフォルトで有効になっています。32ビットのビルドでは、オブジェクト当たり16バイトが追加されます。パディングのサイズを大きくすると、破損によってパッドが影響を受ける可能性が高くなるため、破損を検出できる確率も高くなります。
パッドの大きさはtangosol.coherence.heap.paddingシステム・プロパティを使用して構成できます。このプロパティは、パッドの前後に置くバイト数に設定できます。このシステム・プロパティをゼロ以外の値に設定すると、破損の検出機能が有効になり、リリース・ビルドでも利用できるようになります。
例2-21は、メモリー破損検出の例で得られた結果を示しています。
Coherence 3.5では、共有ライブラリに埋め込まれている実行可能ファイル・クラスを起動するアプリケーション・ラウンチャが追加されました。このラウンチャの使用を前提とすれば、ユーティリティやテスト用実行可能ファイルを共有ライブラリに格納できるので、スタンドアロンの実行可能ファイルのバイナリを別途出荷する必要がありません。
sankaというラウンチャは、ロードする共有ライブラリの名前と実行するクラスの完全修飾名を指定する点で動作がjavaに似ています。
ge: sanka [-options] <native class> [args...]
available options include:
-l <native library list> dynamic libraries to load, separated by : or ;
-D<property>=<value> set a system property
-version print the Coherence version
-? print this help message
指定するライブラリは、オペレーティング・システムのライブラリ・パス(PATH, LD_LIBRARY_PATH, DYLD_LIBRARY_PATH)からアクセス可能であるか、その場所が絶対パスまたは相対パスで指定可能であることが必要です。ライブラリ名は、その動作を表す接頭辞や接尾辞を省略して指定してもかまいません。たとえば、UNIXのlibfoo.soやWindowsのfoo.dllを単にfooと指定できます。アプリケーションのリンク先となっているCoherence共有ライブラリにも、同様にシステムのライブラリ・パスからアクセスできる必要があります。
Coherenceの共有ライブラリには、次のユーティリティ実行可能ファイル・クラスがあります。
coherence::net::CacheFactory: Coherence C++コンソールを実行します。
coherence::lang::SystemClassLoader: 登録済の管理クラスを出力します。
coherence::io::pof::SystemPofContext: 登録済のPOF型を出力します。
このうち、最後の2つの実行可能ファイルでは検査対象とする共有ライブラリを指定することもできます。その場合は、すべてのライブラリではなく、指定したライブラリに存在する登録が出力されます。
|
注意: これまでにサンプルとして出荷されていたコンソールは、組込み実行可能ファイル・クラスとして出荷されるようになりました。 |
当然、これまでと同様に、従来のC++の手法でグローバルなmain関数を使用してアプリケーションを実行可能ファイルにすることができます。また必要に応じて、Sankaを使用して独自のクラスの実行可能ファイルを作成することもできます。実行可能ファイル・クラスの簡単な例を次に示します。
#include "coherence/lang.ns"
COH_OPEN_NAMESPACE2(my,test)
using namespace coherence::lang;
class Echo
: public class_spec<Echo>
{
friend class factory<Echo>;
public:
static void main(ObjectArray::View vasArg)
{
for (size32_t i = 0, c = vasArg; i < c; ++i)
{
std::cout << vasArg[i];
}
}
};
COH_REGISTER_EXECUTABLE_CLASS(Echo); // must appear in .cpp
COH_CLOSE_NAMESPACE2
この例のように、指定のクラスはExecutableClassとして登録しておく必要があり、mainメソッドは次のシグネチャに一致している必要があります。
static void main(ObjectArray::View)
指定するObjectArrayパラメータは、コマンドライン上で実行可能ファイル・クラス名に続く引数に対応するString::Viewオブジェクトの配列です。
libecho.soやecho.dllなどの共有ライブラリにリンクすると、次のようにEchoクラスを実行できます。
> sanka -l echo my::test::Echo Hello World Hello World
Coherenceのexamplesディレクトリには、簡単な共有ライブラリのビルドに使用できるヘルパー・スクリプトbuildlibが収められています。