バリアントの変換システム
-
29-10-2019 - |
質問
ダイナミック言語のメインタイプとして使用されるバリアントクラスを作成しました。最終的には256種類の値の値を許可します(ヘッダーは署名されていないバイトで、実際には20のみが使用されます)。今、私はタイプ間のキャスト/変換を実装したいと思っています。
私の最初の考えはルックアップテーブルでしたが、必要なメモリのせん断量は実装するのが非現実的です。
代替手段は何ですか?今、私は他の人からの研究と提案からのさらに3つの方法を検討しています。
- 数値やコレクションなどの大きなサブセットにタイプをグループ化します。
- CanCast(oto)およびCast(Variant)メソッドを持ち、そのインターフェイスを実装するクラスをリストに追加できるようにする変換インターフェイスを作成します。その後、コンバージョンクラスのいずれかがキャストを実行できるかどうかを確認できます。
- (1)に似ていますが、いくつかのマスタータイプを作成し、キャストは元のタイプからマスタータイプまでの2段階のプロセスです。
最高のシステムは何でしょうか?
編集:私はまだ最高のシステムについて確信が持てないので、私は賞金を追加しました、現在の答えは非常に良いです、そして間違いなく私の+1を手に入れましたが、これをした人がそこにいる人がいるに違いありません。
解決
私のシステムは非常に「重い」(たくさんのコード)が、非常に高速で、非常に豊富な(クロスプラットフォームC ++)。あなたがあなたのデザインをどこまで行きたいかはわかりませんが、ここに私がしたことの最大の部分があります:
DatumState
- 「タイプ」の「列」を保持しているクラスに加えて、ネイティブの価値、これはすべての原始的なタイプの間で「組合」である。 void*
. 。このクラスはすべてのタイプから組み合わされており、任意のネイティブ/プリミティブタイプに使用できます。 void*
タイプ。 」以来enum
「持っている」VALUE_OF
" と "REF_TO
「コンテキスト、このクラスは「完全に含む」ように存在することができます float
(またはいくつかの原始的なタイプ)、または「参照 - 所有者」 float
(または原始タイプ)。 (私は実際に持っています」VALUE_OF
", "REF_TO
"、 と "PTR_TO
「コンテキストが論理的に保存できるように 価値, 、a 参照 - null, 、またはa ポインター - それは - null-or-notでありません, 、そして私がする必要があることを知っています 削除または削除しません.)
Datum
- 完全にaを含むクラス DatumState
, 、しかし、さまざまな「よく知られている」タイプに対応するためにインターフェイスを拡張します( MyDate
, MyColor
, MyFileName
, 、など)これらのよく知られているタイプは、実際には void*
内側 DatumState
メンバー。しかし、enum
「の一部 DatumState
」VALUE_OF
" と "REF_TO
「文脈、それは表現できます」pointer-to-MyDate
" また "value-of-MyDate
".
DatumStateHandle
- (よく知られている)タイプでパラメーター化されたヘルパーテンプレートクラス( MyDate
, MyColor
, MyFileName
, 、など)これは、使用されるアクセターです Datum
よく知られているタイプから状態を抽出します。デフォルトの実装はほとんどのクラスで機能しますが、アクセスの特定のセマンティクスを持つクラスは、このテンプレートクラスの1つ以上のメンバー関数の特定のテンプレートパラメーター化/実装をオーバーライドするだけです。
Macros, helper functions, and some other supporting stuff
- よく知られているタイプの「追加」を私に単純化するために Datum
/Variant
, 、ロジックをいくつかのマクロに集中させ、オペレーターの過負荷などのサポート機能を提供し、コードに他のいくつかの規則を確立することが便利だと思いました。
この実装の「副作用」として、参照と価値のセマンティクス、あらゆるタイプの「ヌル」のオプション、あらゆるタイプの異種コンテナのサポートなど、たくさんの利点がありました。
たとえば、整数のセットを作成してインデックスを作成できます。
int my_ints[10];
Datum d(my_ints, 10/*count*/);
for(long i = 0; i < d.count(); ++i)
{
d[i] = i;
}
同様に、一部のデータ型は、文字列または列挙でインデックス付けされます。
MyDate my_date = MyDate::GetDateToday();
Datum d(my_date);
cout << d["DAY_OF_WEEK"] << endl;
cout << d[MyDate::DAY_OF_WEEK] << endl; // alternative
セットオブアイテム(ネイティブ)、またはセットオブを保存できます。Datum
S(各アイテムを包む)。どちらの場合でも、私は再帰的に「描く」ことができます:
MyDate my_dates[10];
Datum d(my_dates, 10/*count*/);
for(long i = 0; i < d.count(); ++i)
{
cout << d[i][MyDate::DAY_OF_WEEK] << endl;
}
人が私を主張するかもしれない」REF_TO
" と "VALUE_OF
「セマンティクスはやり過ぎですが、「セットアンラッピング」には不可欠でした。
私はこれをしました」Variant
「9つの異なるデザインがあり、私の現在のデザインは「最も重い」(ほとんどのコード)ですが、私が一番好きなもの(かなり小さなオブジェクトのフットプリントで最も速いもの)であり、私のために他の8つのデザインを非難しました。使用する。
私のデザインの「欠点」は次のとおりです。
- オブジェクトにアクセスされます
static_cast<>()
からvoid*
(タイプセーフでかなり高速ですが、間接が必要です。しかし、副作用は、設計がの保管をサポートすることです」null
".) - コンパイルは、介して露出しているよく知られているタイプのために長くなります
Datum
インターフェイス(ただし、使用できますDatumState
よく知られているタイプAPIが必要ない場合)。
あなたのデザインに関係なく、私は次のことをお勧めします:
使用する」
enum
「またはあなたに伝えるための何か」タイプ「、別に」価値「。(私はあなたがそれらを1つに圧縮できることを知っています」int
「またはビットパッキングのあるものですが、それはアクセスが遅く、新しいタイプが導入されるにつれて維持するのが非常に難しいものです。)タイプ固有の(オーバーライド)処理のメカニズムを使用して、操作を集中化するためのテンプレートまたは何かに頼ります(非自明のタイプを処理すると仮定)。
ゲームの名前はです 「新しいタイプを追加するときの簡素化されたメンテナンス」 (または、少なくとも、それは私のためでした)。良い用語論文のように、あなたが書き直し、書き直し、書き直す、 保留または増加 あなたが絶えずあなたの機能 削除する システムを維持するために必要なコード(たとえば、既存の新しいタイプを適応させるために必要な努力を最小限に抑える Variant
インフラストラクチャー)。
幸運を!
他のヒント
似たようなことをしました。
「ヘッダー」に別のバイトを追加して、そのタイプが本当に保存されていることを示すことができます。
Cスタイルのプログラミング言語の例:
typedef
enum VariantInternalType {
vtUnassigned = 0;
vtByte = 1;
vtCharPtr = 2; // <-- "plain c" string
vtBool = 3;
// other supported data types
}
// --> real data
typedef
struct VariantHeader {
void* Reserved; // <-- your data (byte or void*)
VariantInternalType VariantInternalType;
}
// --> hides real data
typedef
byte[sizeof(VariantHeader)] Variant;
// allocates & assign a byte data type to a variant
Variant ByteToVar(byte value)
{
VariantHeader MyVariantHeader;
Variant MyVariant;
MyVariantHeader.VariantInternalType = VariantInternalType.vtByte;
MyVariantHeader.Reserved = value;
memcpy (&MyVariant, &MyVariantHeader, sizeof(Variant));
return myVariant;
}
// allocates & assign a char array data type to a variant
Variant CharPtrToVar(char* value)
{
VariantHeader MyVariantHeader;
Variant MyVariant;
MyVariantHeader.VariantInternalType = VariantInternalType.vtByte;
MyVariantHeader.Reserved = strcpy(value);
// copy exposed struct type data to hidden array data
memcpy(&MyVariant, &MyVariantHeader, sizeof(Variant));
return myVariant;
}
// deallocs memory for any internal data type
void freeVar(Variant &myVariant)
{
VariantHeader MyVariantHeader;
// copy exposed struct type data to hidden array data
memcpy(&MyVariantHeader, &MyVariant, sizeof(VariantHeader));
switch (MyVariantHeader.VariantInternalType) {
case vtCharPtr:
strfree(MyVariantHeader.reserved);
break;
// other types
default:
break;
}
// copy exposed struct type data to hidden array data
memcpy(&MyVariant, &MyVariantHeader, sizeof(Variant));
}
bool isVariantType(Variant &thisVariant, VariantInternalType thisType)
{
VariantHeader MyVariantHeader;
// copy exposed struct type data to hidden array data
memcpy(&MyVariantHeader, &MyVariant, sizeof(VariantHeader));
return (MyVariant.VariantInternalType == thisType);
}
// -------
void main()
{
Variant myVariantStr = CharPtrToVar("Hello World");
Variant myVariantByte = ByteToVar(42);
char* myString = null;
byte myByte = 0;
if isVariantType(myVariantStr, vtCharPtr) {
myString = VarToCharPtr(myVariantStr);
// print variant string into screen
}
// ...
}
これは単なる提案であり、テストされていません。
たぶんあなたはすでに計算を行ったかもしれませんが、ルックアップテーブルに必要なメモリの量はそうではありません それ 多くの。
タイプが互換性があるかどうかを確認する必要がある場合は、(256*256)/2ビットが必要です。これには、4kのメモリが必要です。
変換関数へのポインターも必要な場合は、(256*256)/2ポインターが必要です。これには、32ビットマシンで128kのメモリ、64ビットマシンで256Kが必要です。低レベルのアドレスレイアウトを喜んで行う場合は、おそらく32ビットマシンと64ビットマシンの両方で64Kに下がることができます。