Гибкое преобразование между строкой, int, double для С++ Variant Class

Я реализую вариант-класс (не используя boost), и мне интересно, как вы будете обрабатывать случай, когда вы будете хранить любую строку, целое или двойное и автоматически конвертировать его в соответствии с желаемым типом через 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() works), и вызывать исключение, когда назначенный тип отличается от того, который вы хотите преобразовать.

Извините, что использование boost не является вариантом.

Ответ 1

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

Ответ 2

Чтобы реализовать что-то подобное, вы должны иметь возможность изменять хранимый тип, потому что если пользователь изменяет переменную по ссылке, он хочет, чтобы изменение повлияло на сохраненное значение, поэтому я бы написал что-то вроде этого:

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

...

}

Ответ 3

Я сам реализовал простой вариантный класс (без использования сторонних библиотек). Каждая из функций ToXxx содержит switch над m_type (перечисление, которое указывает тип, который в данный момент удерживается). Для преобразования строк (как из, так и из) я использую std::stringstream. На самом деле это тривиально. Похоже на то, что предложил Mooing Duck.

Р. S. Если предполагается частые вызовы преобразования строк для одного и того же значения, я бы его кешировал.

Ответ 4

Во-первых, вам абсолютно необходимо вернуться по ссылке? Возврат по значению, вероятно, будет проще, поскольку вы можете сделать это в любое время без изменения внутреннего состояния объекта Variant.

Если вам абсолютно необходимо вернуться по ссылке, то вам нужно иметь действительное место памяти для возврата ссылки. (Так, например, возврат ссылки на объект стека не является хорошим, потому что объект стека исчезнет, ​​когда метод ToXXX() вернется, а ссылка будет ссылкой на недопустимую память)

Легкий способ сделать это - включить переменную-член (переменный?) каждого типа в объект Variant и установить значение переменной-члена и при необходимости вернуть ссылку на нее. Конечно, недостатком этого является то, что он делает объекты Variant такими же большими, как сумма всех возможных объектов, но это может быть хорошо, если вам не все равно, что использование памяти.

Если вы также заботитесь о минимизации использования памяти объектов Variant, то вам, вероятно, потребуется использовать объединение (или что-то эквивалентное одному). C будут работать для типов POD, но если вам нужно включить не-POD-типы (например, std::string объекты), их будет недостаточно. Если вам это нужно, вы можете пойти с байтовым буфером (который достаточно велик для максимально возможного типа), и при необходимости использовать новые и явные вызовы деструктора, но это немного сложно реализовать.

Что касается фактических преобразований типов данных (например, "7" → (int) 7 → (double) 7.0, вам просто нужно реализовать логику (возможно, через вложенные операторы switch?)), чтобы выполнить правильная вещь для каждой возможной пары типов "источник" и "назначение". Я не думаю, что есть какой-то волшебный путь вокруг этого, если не использовать функции повышения, когда это было сделано для вас.

Ответ 5

Это единственный способ, о котором я могу думать, если вы хотите сделать конверсии "на лету".

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

Ответ 6

Здесь быстрая и грязная реализация с использованием 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)

Реализация может быть еще более простой с boost::variant<int, double, std::string>, так как вы могли бы вытащить ее одним посетителем с шаблоном operator() вместо того, чтобы писать отдельный путь кода для каждого из разных типов.

Ответ 7

Учитывая то, что вам нужно, я бы предложил загрузить http://qt.nokia.com и изучить реализацию QVariant Class. Если это кажется чрезмерно сложным, я бы предложил что-то вроде этого:

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

Если вам нужны детали реализации для функций преобразования, я могу их предоставить, но я думаю, что они должны быть довольно прямолинейными.

Ответ 8

Чтобы сообщить, что С++ 17 будет реализовывать std:: variant, чтобы сделать это.