文字列リテラルをintern済み文字列に変換

C++からXtalで定義されたメソッドを呼びたいというとき、
objectptr->send("this_is_a_pen");
という風に呼び出すことができます。
ここで、sendが本当に必要なのはintern済み文字列なので、自動的に文字列リテラルがintern済み文字列に変換されています。

C++からのメソッド呼び出しのたびに、文字列リテラルをintern済み文字列に変換するのは効率が悪そうです。

ということで、それを効率的にする仕組みを色々考えてみました。

一番最初は

#ifdef _DEBUG
#define Xid(x) intern(#x) // デバッグ時は文字列を毎回internする
#else
#define Xid(x) id_##x // リリース時は変数参照に置き換える
#endif

ID id_this_is_a_pen; // ツールでXidマクロをscanして自動的に同名の変数を定義する

と書いていました。

しかし、ツールでいちいち生成するというのは使い勝手が悪いです。


次は、文字列リテラルのアドレスをキーとしたハッシュテーブルを持つことにしました。

hashtable<const char*, ID> id_hashtable;
ID make_id(const char* str){
  ID id = id_hashtable[str];
  if(!id){
    // テーブルになかったら本当に生成して登録
    id_hashtable[str] = id = new_id(str);
  }
  return id;
}

#define Xid(x) make_id(#x)

ハッシュテーブルだとメモリ使用量が多く、
衝突しているリテラルが多いと速度も落ちてしまうのが気になります。


文字列リテラルと一対一に対応するユニークな整数値があれば、上のハッシュテーブルを配列に変えることが可能になるはずです。

vector<ID> id_table;
ID make_id(const char* str, int index){
  ID id = id_table[index];
  if(!id){
    // テーブルになかったら本当に生成して登録
    id_table[index] = id = new_id(str);
  }
  return id;
}

というように。

ということで、文字列リテラルと一対一に対応するユニークな整数値を生成する方法を考えました。


型と一対一に対応するユニークな整数値は次のように書けば得られることはわかっていました。

struct CppClassSymbolData{ 
	CppClassSymbolData(){
		static unsigned int counter = 0;
		value = counter++;
	}

	unsigned int value;
};

template<class T>
struct CppClassSymbol{
	static CppClassSymbolData value;
};
template<class T>
CppClassSymbolData CppClassSymbol<T>::value;


CppClassSymbol<Int>::value.value; 
CppClassSymbol<Float>::value.value; 
CppClassSymbol<String>::value.value; 
// 上の三つはそれぞれユニークな整数値となる


文字列リテラルがテンプレート引数にできれば、次のでうまくいくのですが、文字列リテラルをテンプレート引数にするのは不可能です。

struct IdentifierData{ 
	IdentifierData(){
		static unsigned int counter = 0;
		value = counter++;
	}

	unsigned int value;
};

template<const char* STR>
struct Identifier{
	static IdentifierData value;
};
template<const char* STR>
IdentifierData Identifier<STR>::value;

Identifier<"aaa">::value.value; // error
Identifier<"bbb">::value.value; // error
Identifier<"ccc">::value.value; // error

そこで、classをつけて、型名として扱わせることにしました。

struct IdentifierData{ 
	IdentifierData(){
		static unsigned int counter = 0;
		value = counter++;
	}

	unsigned int value;
};

template<class T>
struct Identifier{
	static IdentifierData value;
};

template<class T>
IdentifierData Identifier<T>::value;

vector<ID> id_table;
ID make_id(const char* str, int index){
  ID id = id_table[index];
  if(!id){
    // テーブルになかったら本当に生成して登録
    id_table[index] = id = new_id(str);
  }
  return id;
}

#define Xid(x) make_id(#x, Identifier<class id_##x>::value.value)

うーん。どうなんだろう。