このことをどこで見つけたのかは覚えていませんが、間違えなければ、このテクニックはJim Koplien(別名James O. Coplien)の著書Advanced C ++ Programming Styles and Idiomsで提案されました。
基本クラス(エンベロープ)内に同じタイプのオブジェクト(レター)へのポインターを格納するという考え方です。 この場合、エンベロープは仮想メソッドの呼び出しをレターに「リダイレクト」する必要があります。 良い例では、いつものように小さな問題があるので、魔法のテクニック(または呪文)のシステムを「モデル化」=)各テクニックについて、5つの主要な要素(またはそれらの組み合わせ)の1つが使用され、外の世界とそれが適用される主題の技術。 同時に、タイプに関係なく、すべての技術者と協力できるようにしたいと考えています。
これを行うには、技術の種類ごとに基本スキルクラスとその派生クラスを作成します。 基本クラスでは、純粋に、たとえば、テクニックを作成、表示し、その後にメモリを削除するための3つの仮想メソッドを定義します。
#include <cstdlib>
#include <iostream>
#include <stdexcept>
#include <vector>
using std :: cout ;
using std :: endl ;
enum
{
FIRE = 0x01 ,
WIND = 0x02 ,
LIGHTNING = 0x04 ,
SOIL = 0x08 ,
WATER = 0x10
} ;
class Skill // aka Jutsu =)
{
public :
// virtual (envelope) constructor (see below)
Skill ( int _type ) throw ( std :: logic_error ) ;
// destructor
virtual ~Skill ( )
{
if ( mLetter )
{
// virtual call in destructor!
erase ( ) ;
}
delete mLetter ; // delete Letter for Envelope
// delete 0 for Letter
}
virtual void cast ( ) const { mLetter - > cast ( ) ; }
virtual void show ( ) const { mLetter - > show ( ) ; }
virtual void erase ( ) { mLetter - > erase ( ) ; }
protected :
// letter constructor
Skill ( ) : mLetter ( NULL ) { }
private :
Skill ( const Skill & ) ;
Skill & operator = ( Skill & ) ;
Skill * mLetter ; // pointer to letter
} ;
class FireSkill : public Skill
{
public :
~FireSkill ( ) { cout << "~FireSkill()" << endl ; }
virtual void cast ( ) const { cout << "Katon!" << endl ; }
virtual void show ( ) const { cout << "FireSkill::show()" << endl ; }
virtual void erase ( ) { cout << "FireSkill:erase()" << endl ; }
private :
friend class Skill ;
FireSkill ( ) { }
FireSkill ( const FireSkill & ) ;
FireSkill & operator = ( FireSkill & ) ;
} ;
class WoodSkill : public Skill
{
public :
~WoodSkill ( ) { cout << "~WoodSkill()" << endl ; }
virtual void cast ( ) const { cout << "Mokuton!" << endl ; }
virtual void show ( ) const { cout << "WoodSkill::show()" << endl ; }
virtual void erase ( ) { cout << "WoodSkill::erase()" << endl ; }
private :
friend class Skill ;
WoodSkill ( ) { }
WoodSkill ( const WoodSkill & ) ;
WoodSkill & operator = ( WoodSkill & ) ;
} ;
Skill :: Skill ( int _type ) throw ( std :: logic_error )
{
switch ( _type )
{
case FIRE :
mLetter = new FireSkill ;
break ;
case SOIL | WATER :
mLetter = new WoodSkill ;
break ;
// ...
default :
throw std :: logic_error ( "Incorrect type of element" ) ;
}
// virtual call in constructor!
cast ( ) ;
}
int main ( )
{
std :: vector < Skill * > skills ;
try
{
skills. push_back ( new Skill ( FIRE ) ) ;
skills. push_back ( new Skill ( SOIL | WATER ) ) ;
// skills.push_back(new Skill(LIGHTNING));
}
catch ( std :: logic_error le )
{
std :: cerr << le. what ( ) << endl ;
return EXIT_FAILURE ;
}
for ( size_t i = 0 ; i < skills. size ( ) ; i ++ )
{
skills [ i ] - > show ( ) ;
delete skills [ i ] ;
}
return EXIT_SUCCESS ;
}
原則として、これはそれほど興味深いものではありませんが、結論は次のようになります。
カトン!
モクトン!
FireSkill :: show()
FireSkill:消去()
〜ファイアスキル()
WoodSkill ::ショー()
WoodSkill :: erase()
〜WoodSkill()
何が起こっているのかをよく理解しましょう。
そのため、同じタイプ(文字)のオブジェクトへのポインターを含むSkillクラス(エンベロープ)があります。 コピーコンストラクタと代入演算子は、危害を加えないようにプライベートに隠されます。 主な関心事は、2つのクラスコンストラクターによって表されます。1つはオープンで、もう1つは保護され、デストラクタです。
オープンコンストラクター、彼はEnvelopeのコンストラクターであり、この場合、彼は「仮想コンストラクター」(その定義は以下)であり、1つのパラメーター-「エレメント」のタイプを取ります。これに基づいて、構築されたオブジェクトのタイプが計算されます 入力パラメータに応じて、文字ポインターは特定のオブジェクト(Skillから継承されたFireSkill、WoodSkillなど)へのポインターで初期化されます。 入力パラメーターに無効な値がある場合、例外がスローされます。
派生クラスでは、FireSkill、WoodSkillなど。 コンストラクタはデフォルトで閉じられますが、基本Skillクラスはfriendとして宣言されます。これにより、Skillクラス内でのみこれらのクラスのオブジェクトを作成できます。 これらのクラスのコピーコンストラクターと代入演算子は閉じられており、未定義です。 Skillクラスのすべての仮想メソッドは、デリバティブでオーバーライドされます。
仮想コンストラクタは、派生クラスのオブジェクトがその内部に作成されるため、前方宣言で急上昇する必要がないように、すべての派生クラスの下に定義する必要があります。
派生クラスのオブジェクトが仮想コンストラクターで作成される場合、これらのオブジェクトを構築するとき、基本クラスのデフォルトコンストラクターを最初に呼び出す必要があります。 基本クラスのデフォルトコンストラクターは、文字へのポインターをゼロで初期化するだけです。 実際、このコンストラクターはレターコンストラクターであり、mLetterにゼロを書き込むことで示します。
仮想メソッドの呼び出しはどうですか? 基本クラスでは、仮想メソッド内に「リダイレクト」があります。実際、Envelopeは、Writingメソッドを単に呼び出すラッパーの役割を果たします。 Writingメソッドはポインターを介して呼び出されるため、遅延バインディングが発生します。つまり、呼び出しは仮想になります。 さらに! コンストラクターとデストラクターでメソッドを仮想的に呼び出すことができます。Skillオブジェクトを作成するとき、このクラスのパラメーター化されたコンストラクターを呼び出します。このコンストラクターは、Letterを構築し、mLetterを初期化します。 その後、cast()を呼び出します。その内部には、mLetter-> cast()の呼び出しがあります。 mLetterはこの時点ですでに初期化されているため、仮想呼び出しが進行中です。
〜Skill()デストラクタでも同じことが言えます。 まず、mLetterが初期化されているかどうかを確認します。 そうであれば、エンベロープデストラクターにいるので、エンベロープストリップメソッドを仮想的に呼び出してから削除します。 そうでない場合は、エンベロープデストラクタに移動し、delete 0が実行されます(この構造は完全に安全です)。
重要なポイント:
- すべてのオブジェクトが1つのコンストラクターを介して作成され、基本クラスのオブジェクトを操作するようになりました。 すべての仮想呼び出しはクラス自体の内部にあります。 スタック上にSkillクラスのオブジェクトを作成することもできます。このオブジェクトのメソッドは、仮想のように機能します。
- コンストラクタとデストラクタでは、仮想メソッド呼び出しを使用できます。
- 基本クラスは、すべての仮想メソッドを派生クラスで再定義する必要があるため、ある程度抽象的です。 これに失敗すると、たとえば、mLetter-> cast()は、NULLポインターメソッドを呼び出すこと以外の何物でもありません。
- 仮想コンストラクターが呼び出されると、作成されたオブジェクトのタイプは、コンパイル段階ではなく実行段階で実際に決定されます。 ただし、このような呼び出しはtry-catchブロックで囲む必要があります。そうしないと、例外をスキップできます。
- 別の仮想メソッドを基本クラスに追加する場合、すべての派生クラスで再定義する必要があります。
誰かが便利になることを願っています;)
フィン。