为C ++ Variant Class灵活地在string,int,double之间进行转换

时间:2011-11-17 19:54:12

标签: c++

我正在实现一个变体类(不使用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()工作),并在分配的类型与您要转换的类型不同时引发异常。

抱歉,使用提升不是一种选择。

8 个答案:

答案 0 :(得分:11)

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

答案 1 :(得分:7)

要实现这样的东西,你需要能够改变存储的类型,因为如果用户通过引用更改变量,他希望更改影响存储的值,所以我写这样的东西:< / p>

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

...

}

答案 2 :(得分:2)

我自己实现了一个简单的变体类(不使用第三方库)。每个ToXxx函数都包含switch超过m_type(枚举,表示当前持有的类型)。对于字符串转换(包括from和to),我使用std::stringstream。这真是微不足道。非常像 Mooing Duck 建议。

P上。 S.如果打算频繁调用相同值的字符串转换,我会缓存它。

答案 3 :(得分:1)

首先,你绝对需要通过引用返回吗?按值返回可能会更容易,因为您可以随时执行此操作,而无需更改Variant对象的内部状态。

如果您确实需要通过引用返回,那么您需要有一个有效的内存位置来返回引用。 (例如,返回对堆栈对象的引用并不好,因为当ToXXX()方法返回时堆栈对象将消失,并且引用将是对无效内存的引用)

这样做的简单方法是在Variant对象中包含每个类型的(可变?)成员变量,并设置该成员变量的值并根据需要返回对它的引用。当然,这样做的缺点是它使你的Variant对象和所有可能对象的总和一样大,但如果你不太关心内存使用情况,这可能没问题。

如果你也关心最小化Variant对象的内存使用量,那么你可能需要使用一个union(或类似的一个)。 C联合会适用于POD类型,但如果你需要包含非POD类型(例如std :: string对象),它们就不够了。如果需要,可以使用字节缓冲区(足够大以容纳最大可能类型)并在必要时使用placement-new和explicit析构函数调用,但实现起来有点繁琐。

就数据类型的实际转换而言(例如“7” - &gt;(int)7 - &gt;(double)7.0,您只需实现逻辑(可能通过嵌套的switch语句?)为每一对可能的“源”和“目的地”类型做正确的事情。我认为没有任何神奇的方法,除了使用提升功能之外,它已经为你完成了。

答案 4 :(得分:1)

如果您希望能够即时进行转换,这是我能想到的唯一方法。

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

答案 5 :(得分:1)

使用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()的单个访问者将其拉出来,而不必为每个访问者编写单独的代码路径不同类型。

答案 6 :(得分:1)

只是要知道C ++ 17将实现std::variant来执行此操作。

答案 7 :(得分:0)

根据您的需要,我建议下载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;
};

如果您需要转换功能的实现细节,我可以提供它们,但我认为它们应该非常简单。