値と型再び

ゲームに簡単なプログラムを組み込むためにスクリプト言語とそれを実行するインタープリタをC++で作ってしまおうというコーナーです。
前回、パーサのアルゴリズムについて大雑把に考えてみました。
ですが今回は第10回の続きです。

一つの型に複数の役割を

C++側での話です。
一つの型に複数の役割を持たせるため、第10回ではなんとなく思いついた派生クラスを使っていましたが、これは本当の意味で「一つの型に複数の役割を持たせる」ことにはなっていませんでした。
この方法は異なる型を同じように利用できるという利点があるのですが、それでも違う型は違う型なので、値のコピーができませんでした。
そんなわけで、複数の型を一つのメモリ領域に押し込めてしまう共用体というものを使うことにします。
ゲームばっかり作ってたら偏った知識ばかりがついてしまってこういうのを最近まで知らなかったんです。
共用体というのはこんな感じのものです。

union UData {
    double dValue;
    std::string strValue;
} uData1, uData2;

uData1.dValue = 1.0;             // double型を代入
uData2 = uData2;                 // 値をコピー

double d = uData1.dValue;        // double型を取り出す
std::string s = uData1.strValue; // string型は入っていないので不正

uData1.strValue = "ゴマゴマ";    // string型を代入(double型のデータは無くなる)
d = uData1.dValue;               // double型はもう入っていないので不正
s = uData1.strValue;             // これはもちろんOK
//でも実はunionにstringは入れられないのよね…

というわけで、共用体には複数のデータ型のうち、一つの型のデータが入っています。
しかし共用体自身はどの種類のデータが入っているかは記憶していないので、中身の種類を示す変数と一緒に構造体に入れるとよさそうです。

struct TData {
    enum EDataType {
        eDouble,
        eString,
    } eDataType;
    union UData {
        double dValue;
        std::string strValue;
    } uData;
} tData;

ちなみに、共用体というのは、中に入るデータのうち一番大きいものに合わせてサイズが決まりますので、中に入るデータのサイズがあまりにちぐはぐだと結構もったいないことになってしまいます。
以下はやや極端な例です。

union {
    BYTE onebyte;
    BYTE millionbytes[1000000];
};

onebyteにデータを入れると残りの999999バイトがもったいないですね。
だから、中に入るデータが大きくなりそうなときは、データそのものは他の場所で管理しておいて、共用体の中にはそのデータへのポインタだけを入れておくとかの工夫ができそうです。
文字列とか配列とか、膨れ上がりそうです。

プログラム書き換え

実際にはメンバ関数とかコンストラクタとかデストラクタとかも付けたいんでクラスに放り込んでみます。
値の仕様が変わると当然それを使っていた部分も手直しが必要になるので、それは第19回で修正します。

class CMSValue {
protected:
    enum EMSValueType {
        eMSNullValue,
        eMSNumericalValue,
        eMSStringValue,
        eMSArrayValue,
        eMSErrorValue,
    } m_eType;	// 値の型
    union UMSValue {
        double dValue;
        // 他のデータは後で考えるねー
    } m_uValue;	// 値の実体
public:
    CMSValue() { m_eType = eMSNullValue; }
    CMSValue(double dValue) {
        m_eType = eMSNumericalValue;
        m_uValue.dValue = dValue;
    }
};