JavaCCとJJTreeを使ってjavaコードをC++へ変換 その2

第1回目


前回のエントリにあった、System::new_array/System::delete_arrayの実装について説明します。


javaでは以下のように要素の数が異なる配列の作成が可能です。

int v[][] = 
{
  { 0, 1, 2},
  { 0, 1}
};

これはC++では以下のように表現できます。

int **v = new int*[2];
v[0] = new int[3];
v[1] = new int[2];
v[0][0] = 0;v[0][1] = 1;v[0][2] = 2;
v[1][0] = 0;v[1][1] = 1;


C++に変換する上でGCは実装しないことにしていたので、これらもきちんと破棄する必要があります。

delete v[0];
delete v[1];
delete v;


ここで、vに対して操作して全てが破棄できれば便利ですよね?


最終的には以下のようなコードに変換します。

//生成
int **v = static_cast<int**>(System::new_array(2, 2 , sizeof(int*)) );
v[0] = static_cast<int*>(System::new_array(1, 3 , sizeof(int)) );
v[1] = static_cast<int*>(System::new_array(1, 2 , sizeof(int)) );

//破棄
System::delete_array<int*>(v);


vがSystem::delete_arrayに渡される時、int*の要素数がわからなければdelete v[0];とdelete v[1];を呼ぶことが出来ません。


そこで、System::new_array時にそれぞれの要素の数と要素の大きさ、ポインタ参照の深さを記録するようにします。


new_arrayの第1引数はポインタ参照の深さ、第2引数は要素数、第3引数は要素の大きさです。


実際にそれぞれの実装をお見せします。
MALLOCを読み替えればBREW以外でも利用可能です。

static void* new_array(int depth, int elementnum, size_t element_size){
  void *ptr = MALLOC(elementnum * element_size + sizeof(int) * 3);
  *reinterpret_cast<int*>(ptr) = depth;
  *(reinterpret_cast<int*>(ptr) + 1) = elementnum;
  *(reinterpret_cast<int*>(ptr) + 2) = element_size;
  return reinterpret_cast<int*>(ptr) + 3;
}

template<typename T>
static void _delete_array(void *ptr, int depth){
  if( ptr == NULL ) return;
  int elementnum = *(reinterpret_cast<int*>(ptr) - 2);
  int element_size = *(reinterpret_cast<int*>(ptr) - 1);
  if( depth == 1 ){
    LIB_ASSERT( elementnum > 0 );
    if( elementnum == 1 ){ //newして確保した領域
      delete reinterpret_cast<T>(ptr); //(int*)ptr - 3をFREEします
    }else{ //プリミティブな型の配列とか
      FREE(reinterpret_cast<int*>(ptr) - 3);
    }
  }else if( depth > 1 ){
    for(int i=0; i<elementnum; i++){
      _delete_array<T>(
        *reinterpret_cast<void**>(reinterpret_cast<char*>(ptr)
         + i * element_size), depth - 1);
    }
    FREE(reinterpret_cast<int*>(ptr) - 3);
  }
}
template<typename T>
static void delete_array(void *ptr){
  if( ptr == NULL ) return;
  int depth = *(reinterpret_cast<int*>(ptr) - 3);
  _delete_array<T>(ptr, depth);
}
static inline
int size_array(void *ptr){
  return *(reinterpret_cast<int*>(ptr) - 2);
}

//newとdeleteはカスタマイズしたものだけ記載しています
void* operator new(size_t size) throw(){
  void* ptr = MALLOC(size + sizeof(int) * 3);
  *reinterpret_cast<int*>(ptr) = 1;
  *(reinterpret_cast<int*>(ptr) + 1) = 1;
  *(reinterpret_cast<int*>(ptr) + 2) = size;
  return reinterpret_cast<int*>(ptr) + 3;
}

void operator delete(void* address) throw(){
  address = reinterpret_cast<int*>(address) - 3;
  FREE(address);
  return;
}


int型などプリミティブな型では、末端の要素は値で保持しますが、それ以外のクラスのインスタンスはポインタで保持します。

//生成
foo *v = System::new_array(2, 2, sizeof(foo*));
v[0] = new foo();
v[1] = new foo();
//破棄
System::delete_array<foo*>(v);

同じ1次元の配列でも非プリミティブ型ではポインタ参照の深さは2、プリミティブであれば1となります。


この場合インスタンスの生成にはそのままnewを用います。
末端の要素であるv[0]やv[1]にはdelete reinterpret_cast(v[0])に相当するものが呼ばれます。