開く C++ バリアント クラスの文字列、int、倍精度浮動小数種の間で柔軟に変換




股関節 ストレッチ (7)

バリアントクラスを実装しています(boostを使用していません)。文字列、整数、またはdoubleのいずれかを格納し、ToString()を使用してそれを目的の型に自動的に変換する方法を考えてみてください。 ToInt()またはToDouble()。

例えば、

Variant a = 7;
cout << "The answer is" + a.ToString() << endl; // should print "The answer is 7"
a = "7.4";
double &b = a.ToDouble();
b += 1;
cout << a.ToString() << endl; // should print 8.4

ToXXX関数は、変換先の型の参照を返すはずです。 今、私はそれが最初に割り当てられたのと同じ型を返すことができるコードを持っています( Variant a = Int(7); a.ToInt()うまくVariant a = Int(7); a.ToInt()に変換したいです。

boostを使って申し訳ありませんが、選択肢ではありません。


    #include <string>
    #include <iostream>
    class Variant{
    public:
        Variant(){
            data.type = UNKOWN;
            data.intVal = 0;
        }
        Variant(int v){
            data.type = INT;
            data.intVal = v;
        }
        Variant(double v){
            data.type = DOUBLE;
            data.realVal = v;
        }
        Variant(std::string v){
            data.type = STRING;
            data.strVal = new std::string(v);
        }
            //the missing copy constructor
             Variant(Variant const& other)
             {
                *this = other;// redirect to the copy assignment
              }

        ~Variant(){
            if(STRING == data.type){
                delete data.strVal;
            }
        }

        Variant& operator = (const Variant& other){
            if(this != &other)
            {
                if(STRING == data.type){
                    delete data.strVal;
                }

                switch(other.data.type){
                case STRING:{
                        data.strVal = new std::string(*(other.data.strVal));
                        data.type = STRING;
                        break;
                    }
                default:{
                        memcpy(this, &other, sizeof(Variant));
                        break;
                    }           
                }
            }
            return *this;
        }
        Variant& operator = (int newVal){
            if(STRING == data.type){
                delete data.strVal;
            }
            data.type = INT;
            data.intVal= newVal;
            return *this;
        }

        Variant& operator = (double newVal){
            if(STRING == data.type){
                delete data.strVal;
            }
            data.type = DOUBLE;
            data.realVal= newVal;
            return *this;
        }

        Variant& operator = (std::string& newVal){
            if(STRING == data.type){
                delete data.strVal;
            }
            data.type = STRING;
            data.strVal= new std::string(newVal);
            return *this;
        }
        operator int&() {
            if(INT == data.type)
            {
                return data.intVal;
            }
            //do type conversion if you like
            throw std::runtime_error("bad cast");
        }

        operator double&()  {
            if(DOUBLE == data.type){
                return data.realVal;
            }
            //do type conversion if you like
            throw std::runtime_error("bad cast");
        }
        operator std::string&() {
            if(STRING == data.type){
                return *data.strVal;
            }
            throw std::runtime_error("bad cast");
        }
    private:
        enum Type{
            UNKOWN=0,
            INT,
            DOUBLE,
            STRING
        };
        struct{
            Type type;
            union 
            {
                int intVal;
                double realVal;
                std::string* strVal;
            };
        } data;
    };


    int main(){
        Variant v("this is string");//string
        v=1;//int
        v=1.0;//double
        v=std::string("another string");//
        Variant v2; //unkown type
        v2=v;//string
        std::cout << (std::string&)v2 << std::endl;
        return 0;
    }    

私は自分自身で(サードパーティのライブラリを使用せずに)単純なバリアントクラスを実装しました。 ToXxx各関数には、 m_type (現在保持されているタイプを示すenum)のswitchが含まれています。 文字列変換(fromとtoの両方)にはstd::stringstreamを使います。 それは本当に簡単です。 Mooing Duckが提案したようなものです

PS同じ値のための文字列変換の頻繁な呼び出しが意図されているのであれば、私はそれをキャッシュします。


あなたがその場で変換をしたいのなら、これが私が考えることができる唯一の方法です。

class Variant {
    enum internaltype {stringtype, inttype, doubletype} curtype;
    std::string strdata; //can't be in union
    union {
        int intdata;
        double doubledata;
    };
public:
    Variant() :curtype(inttype) {}
    Variant(const std::string& s) : curtype(stringtype), strdata(s) {}
    Variant(int i) : curtype(inttype) {intdata = i;}
    Variant(double d) : curtype(doubletype) {doubledata = d;}

    std::string& ToString() {
        std::stringstream ss;
        switch(curtype) { 
        case inttype: 
                      ss << intdata;
                      ss >> stringdata;
                      break;
        case doubletype: 
                      ss << doubledata;
                      ss >> stringdata;
                      break;
        }
        curtype = stringtype;
        return &strdata;
    }
    int& ToInt() {/*much the same*/}
    double& ToDouble() {/*much the same*/}
};

あなたが必要としているものを考えると、私はhttp://qt.nokia.comをダウンロードしてQVariantクラスの実装を調べることをお勧めします。 それが過度に複雑に思えるなら、私はこのような何かを提案するでしょう:

class Variant
{
private:
    enum data_type {
     ...
    };

    data_type variant_type;

    union Data {
       char *string;
       int integer;
       double dbl;
    } data;

public:
    Variant(const int data);
    Variant(const double data);
    Variant(const char *data);

    //  I think that implementation of the constructors should be pretty straight forward.

    int ToInt() const; 
    double ToDouble() const; 
    std::string ToString() const;
};

変換の実装はここから簡単です。

もっとシンプルにしてパフォーマンスがそれほど問題にならない場合は、バリアントに格納されているデータを文字列に変換してからToメソッドが呼び出されたときに元に戻すことができます。

class Variant
{
private:
    char data_string[16384];

public:
    Variant(const int data);
    Variant(const double data);
    Variant(const char *data);

    //  I think that implementation of the constructors should be pretty straight forward.

    int ToInt() const; 
    double ToDouble() const; 
    std::string ToString() const;
};

変換関数の実装の詳細が必要な場合はそれを提供できますが、それらはごく簡単なものであるべきだと思います。


C ++ 17がこれを行うためにstd::variantを実装することを知らせるためだけに。


このようなものを実装するには、ストアドタイプを変更できるようにする必要があります。ユーザーが参照によって変数を変更した場合、その変更がストアド値に影響を与えることを望んでいるためです。

class Variant
{
private:
    enum StoreType
    {
        Integer,
        Float,
        String,
    }
    store_type;

    union
    {
       int * as_integer;
       double * as_double;
       std::string * as_string;
    }
    store_pointer;

    // convert to type
    void integer_to_double();
    void integer_to_string();
    void double_to_integer();
    void double_to_string();
    void string_to_integer();
    void string_to_double();

public:

...

    int & ToInt()
    {
        switch (store_type)
        {
            case Integer: break;
            case Double: double_to_integer(); break;
            case String: string_to_integer(); break;
        }
        return * as_integer;
    }

...

}

最初に、あなたは絶対に参照によって戻る必要がありますか? Variantオブジェクトの内部状態を変更しなくてもいつでも値を返すことができるので、値で返す方がおそらく簡単でしょう。

あなたが絶対に参照によって戻る必要があるならば、あなたは参照を返すために有効なメモリ位置を持つ必要があります。 (例えば、ToXXX()メソッドが戻ったときにスタックオブジェクトがなくなり、その参照が無効なメモリへの参照になるため、スタックオブジェクトへの参照を返すことは意味がありません。)

そのための簡単な方法は、Variantオブジェクトに各型の(可変の?)メンバ変数を含め、そのメンバ変数の値を設定し、必要に応じてそれへの参照を返すことです。 もちろん、その欠点は、Variantオブジェクトがすべての可能なオブジェクトの合計と同じ大きさになることですが、メモリの使用量をあまり気にしなくても問題ない場合があります。

Variantオブジェクトのメモリ使用量を最小限に抑えることにも関心がある場合は、おそらく共用体(またはそれと同等のもの)を使用する必要があります。 Cの共用体はPOD型のために働くでしょう、しかしあなたが非POD型(例えばstd :: stringオブジェクト)を含める必要があるならそれらは十分ではないでしょう。 それが必要ならば、byte-bufferを使って(可能な限り最大の型が収まるように十分に大きい)、必要に応じてplacement-newおよび明示的なデストラクタ呼び出しを使用することができますが、実装が少し面倒です。

データ型の実際の変換(例えば "7" - >(int)7 - >(double)7.0)に関しては、正しいことをするために(おそらく入れ子になったswitchステートメントを介して)ロジックを実装する必要があります。 「source」型と「destination」型のそれぞれの可能性のあるペアそれを回避するための魔法のような方法はないと思います。





c++