c++ - 開く - 股関節 ストレッチ




C++バリアントクラス用に文字列、int、doubleの間で柔軟に変換 (6)

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

バリアントクラスを実装しています(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を使って申し訳ありませんが、選択肢ではありません。


boost::anyは、カスタムクラスの代わりにboost::anyをバリューホルダーとして使用した、迅速で汚い実装です。 ご覧のとおり、テンプレートを使用すると、コード量をいくらか短く抑えることができます。

ToXXX関数は、格納されている値の基本型を変更し(実際の変換が不要な場合を除く)、それからその参照を返します。 boost::lexical_castを使って変換が行われる、これはあなたの目的には全く適していないかもしれない(lexical_castの非常に厳しい条件に従って変換が成功しなかった場合は例外を投げるだろう)。

#include <boost/any.hpp>
#include <boost/lexical_cast.hpp>
#include <string>
#include <typeinfo>

class Variant
{
    boost::any value;
public:
    Variant(int n): value(n) {}
    Variant(double d): value(d) {}
    Variant(const std::string& s): value(s) {}
    Variant(const char* s): value(std::string(s)) {} //without this, string literals create ambiguities

    int& ToInt() { return convert<int>();}
    double& ToDouble() { return convert<double>(); }
    std::string& ToString() { return convert<std::string>(); }

private:
    template <class T>
    T& convert()
    {
        if (typeid(T) != value.type()) { //otherwise no conversion required
            if (typeid(int) == value.type()) {
                value = boost::lexical_cast<T>(boost::any_cast<int>(value));
            }
            else if (typeid(double) == value.type()) {
                value = boost::lexical_cast<T>(boost::any_cast<double>(value));
            }
            else if (typeid(std::string) == value.type()) {
                value = boost::lexical_cast<T>(boost::any_cast<std::string>(value));
            }
        }
        return *boost::any_cast<T>(&value);
    }
};

#include <iostream>
using namespace std;

int main()
{
    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
}

(実施中: http://codepad.org/C3l5AXg3 : http://codepad.org/C3l5AXg3

boost::variant<int, double, std::string> 、実装はもっと簡単になるかもしれない。別々のコードパスを書く代わりに、テンプレート化されたoperator()単一の訪問者でそれをやってのけることができるからさまざまな種類のそれぞれのために。


あなたが必要としているものを考えると、私は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;
};

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


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

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;
    }

...

}

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

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


    #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;
    }    




c++