忍者ブログ

神戸電子専門学校ゲームソフト学科の生徒が運営するGESのブログです。

   

[PR]

×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

C++でパラメータ配列を実装する

どうも克兎です。

今年の仮面ライダーの夏映画、
見てきました(*’ω`*)

キョーダインが敵ってのは、
特撮ファンから見たらなかなか新鮮ですね。

逆にブラックナイトが味方だったっていうのも不思議な気分です。

さて、今回はパラメータ配列について書いていこうと思います。

拍手[0回]


まず、パラメータ配列とは何か、
パラメータ配列とは、VBやC#、Javaなどで実装されている、
可変長引数的機能です。

引数に、特定の参照型を専用の構文(言語によって様々)で
指定することで、
引数の数を可変長にすることができ、
その引数を、配列として扱うことで、
すべての引数を参照できるようになっています。

C言語の可変長引数と異なる点は、

C言語では、引数がスタックの中に溜め込まれ、マクロ計算によって、
頭から順番に取り出さなければいけないのに対し、
パラメータ配列は、何番目の引数でも自由に取り出すことができます。

また、C言語では、可変長引数に入っている型情報を正確に把握しないと行けないのに対し、
パラメータ配列では引数の型を制限することで、可変長引数についてまわるバグを回避できます。

そしてさらに、パラメータ配列は引数の数が分かります

これだけの便利機能があるのに、
C++にはC言語の可変長引数しか用意されていません。

というわけで、実装してやりましょう。

template< int STACK_SIZE >
class CVaArg{

 public:

  //コンストラクタ
  CVaArg( void ){

   _Len = 0;

  }

  //デストラクタ
  ~CVaArg( void ){

  }
  __declspec( property( get = GetLen ) ) int Len;
  int GetLen( void ){ return _Len; }

 private:

  //データ数
  int _Len;

  //スタックの大きさ
  BYTE* _Stack[ STACK_SIZE ];

};
 

まず、データの保持に使用する、スタックを作ります。
2次元配列のイメージで、
BYTE*型の配列を作成します。
配列長は、テンプレート引数で指定します。
実行速度を優先するためです。

また、引数の数を示す、Len値をプロパティにしてあります。

 

template< int STACK_SIZE >
class CVaArg{

 public:

  //コンストラクタ
  CVaArg( void ){

   _Len = 0;

  }

  //デストラクタ
  ~CVaArg( void ){

   //メモリの解放
   for( int i = 0 ; i < _Len ; i++ ){

    delete[] _Stack[ i ];

   }

  }


  //カンマ演算子のオーバーロード
  template< class TYPE >
  CVaArg& operator ,( TYPE Arg ){

   //スタックサイズに応じて入力数を制限する
   if( _Len < STACK_SIZE ){
   
    //メモリを確保
    _Stack[ _Len ] = new BYTE[ sizeof( TYPE ) ];

    //データをそのまま移す
    memcpy( _Stack[ _Len ] , &Arg , sizeof( TYPE ) );

    //カウンタを回す
    _Len++;

   }

   return *this;

  }

  __declspec( property( get = GetLen ) ) int Len;
  int GetLen( void ){ return _Len; }

 private:

  //データ数
  int _Len;

  //スタックの大きさ
  BYTE* _Stack[ STACK_SIZE ];

};


続いて、スタックにデータを追加する仕組みです。
引数っぽく扱うために、カンマ演算子のオーバーロードを行い、
その中で、引数として与えられた型のサイズ分スタック内に領域を作り
そこに引数のデータをコピーします。
自身を戻り値にすることで、連続してカンマ演算子を使用することができます。
Len値は引数が追加される度にインクリメントします。

デストラクタではスタックの中身を解放します。


続いて、引数を受け取る部分です。

template< int STACK_SIZE >
class CVaArg{

 public:

  //コンストラクタ
  CVaArg( void ){

   _Index = -1;
   _Len = 0;

  }

  //デストラクタ
  ~CVaArg( void ){

   //メモリの解放
   for( int i = 0 ; i < _Len ; i++ ){

    delete[] _Stack[ i ];

   }

  }

  //カンマ演算子のオーバーロード
  template< class TYPE >
  CVaArg& operator ,( TYPE Arg ){

   //スタックサイズに応じて入力数を制限する
   if( _Len < STACK_SIZE ){
   
    //メモリを確保
    _Stack[ _Len ] = new BYTE[ sizeof( TYPE ) ];

    //データをそのまま移す
    memcpy( _Stack[ _Len ] , &Arg , sizeof( TYPE ) );

    //カウンタを回す
    _Len++;

   }

   return *this;

  }

  //データ取り出しインデックスセッター
  CVaArg& operator[]( int Index ){

   //インデックスを設定
   _Index = Index;

   return *this;

  }

  //キャスト
  template< class CAST >
  operator CAST&( void ){

   //データを返す
   return *reinterpret_cast< CAST* >( _Stack[ _Index ] );

  }

  __declspec( property( get = GetLen ) ) int Len;
  int GetLen( void ){ return _Len; }

 private:

  //データ数
  int _Len;

  //インデックス
  int _Index;

  //スタックの大きさ
  BYTE* _Stack[ STACK_SIZE ];

};

配列のように扱うには、[]演算子をオーバーロードする必要があります。
しかし、スタックに詰め込まれているデータの型が不定のため、
戻り値のみをオーバロードする必要があります。
今回、[]演算子をセッターとして扱い、インデックス値を保持し、
自身を戻り値とすることで、
「キャスト演算子のオーバーロード」によって戻り値のオーバーロードを実現しています
(戻り値のオーバーロードをする場合は戻り値専用の型を作るほうがいいのですが、
私はforeach文の機構を作るためにこういう形を取っています。)


これで、パラメータ配列としての機能は使えますが、
これでは、本来の型と異なる型で、データを受け取る事ができてしまいます。
その点を考慮し、
デバッグモード用の実行時エラーとして、
「引数として渡した場合と型が異なる場合はプログラムを落とす」という仕組みを作ります。

 

template< int STACK_SIZE >
class CVaArg{

 public:

  //コンストラクタ
  CVaArg( void ){

   _Index = -1;
   _Len = 0;

  }

  //デストラクタ
  ~CVaArg( void ){

   //メモリの解放
   for( int i = 0 ; i < _Len ; i++ ){

    delete[] _Stack[ i ];

   }

  }


  //カンマ演算子のオーバーロード
  template< class TYPE >
  CVaArg& operator ,( TYPE Arg ){

   //スタックサイズに応じて入力数を制限する
   if( _Len < STACK_SIZE ){
   
    //メモリを確保
    _Stack[ _Len ] = new BYTE[ sizeof( TYPE ) ];

#ifdef _DEBUG
    //型情報を取得
    _Types[ _Len ] = &typeid( TYPE );

#endif
    //データをそのまま移す
    memcpy( _Stack[ _Len ] , &Arg , sizeof( TYPE ) );

    //カウンタを回す
    _Len++;

   }

   return *this;

  }

  //データ取り出しインデックスセッター
  CVaArg& operator[]( int Index ){

   //インデックスを設定
   _Index = Index;

   return *this;

  }

  //キャスト
  template< class CAST >
  operator CAST&( void ){

#ifdef _DEBUG
   //データ型が違う場合、強制的に、プログラムを終了させる
   if( typeid( CAST ) != *_Types[ _Index ] ){

    //メッセージボックスを出して自爆
    char buff[ 256 ];
    sprintf_s( buff , "データの型情報が一致しません。\n%sと%sの比較です。" , typeid( CAST ).name() , _Types[ _Index ]->name() );
    MessageBox( NULL , buff , "型を確認してください" , MB_OK );
    *( int* )0 = 0;

   }

#endif

   //データを返す
   return *reinterpret_cast< CAST* >( _Stack[ _Index ] );

  }

  __declspec( property( get = GetLen ) ) int Len;
  int GetLen( void ){ return _Len; }

 private:

  //データ数
  int _Len;

  //インデックス
  int _Index;

  //スタックの大きさ
  BYTE* _Stack[ STACK_SIZE ];

#ifdef _DEBUG
  //型情報の保持領域
  const type_info* _Types[ STACK_SIZE ];

#endif

};


RTTI(実行時型情報)を用いて型を比較しています。
type_info構造体の==演算子を使い、型情報を比較しています。
RTTIを利用する機構(typeid,dynamic_cast等)は、遅いそうなので、
デバッグ時しか機能しないようにしたほうが無難です。

しかし、データ取得前に型情報が知りたい場合は、
デバッグモード専用の仕組みでなくても良いかもしれません。

では、このクラスの使い方ですが、


void Func( CVaArg< 6 >& Args_ ){

 for( int i = 0 ; i < Args_.Len ; i++ ){
  printf( "%d " , ( int )Args_[ i ] );
 }

}

int main( void ){

 Func( ( CVaArg< 6 >() , 0 , 1 , 2 , 3 ) )

}
 


Func関数に渡している引数を見てください、
関数の()の内側に更に()を書いて、その中にパラメータ配列のコンストラクタと、引数を書きます
テンプレート引数にあるのは、引数の最大値で、
それに続けて引数を書き連ねます。
あくまで最大値なので、それより少なくても、構いません。

Func関数側では、データ数だけ値を表示します。
printfには暗黙キャストが使えないため、明示的にキャストしています。

実はこのクラスは、これだけの機能ではなく

・動的な引数の追加
・インデックス設定後の遅延データ取得
・↑を利用したforeach的機能

等、いろいろな事ができるように設計してます。
実際に制作で使っているのは、もっと色々盛り込んだものですが、
こういった仕組みを作るのも面白いです。

そ~れ~で~は~(*’∀`)ノ

PR

COMMENT

NAME
TITLE
MAIL(非公開)
URL
EMOJI
Vodafone絵文字 i-mode絵文字 Ezweb絵文字
COMMENT
PASS(コメント編集に必須です)
SECRET
管理人のみ閲覧できます

ブログ内検索

最新コメント

[01/29 人面犬]
[10/01 8ch]
[09/12 uncle]
[09/10 某卒業生]
[06/07 uncle]

カレンダー

11 2019/12 01
S M T W T F S
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31

テスト

Copyright ©  -- GESブログ --  All Rights Reserved
Design by CriCri / Photo by Geralt / powered by NINJA TOOLS / 忍者ブログ / [PR]