如何改变一个c++对象的类(实现可变类型)



首先:我知道更改对象的类通常是一个坏主意,但我正在实现自己的编程语言,它的变量可以包含任何类型的值,甚至可以随意更改它们的类型,所以请假设我不是一个不了解OO基础的初学者。

目前,我在C中实现了我的变量。每个变量都有一个指向函数指针表的指针,包含像SetAsInt(), SetAsString()等函数,然后是c++中的实例变量。所有对象的大小相同。

当一个变量包含字符串并且有人给它赋值Int时,我手动调用析构函数,修改函数指针表,使其指向可变Int值所用的表,然后设置其Int实例变量。

这有点难以维护,因为每次添加新类型时,我都必须添加一个新的函数指针表,并将中的所有函数指针填满。函数指针的结构似乎没有进行非常糟糕的类型检查,缺少字段也不会引起抱怨,所以我很容易不小心忘记列表中的一个指针,从而导致有趣的崩溃。此外,我必须重复大多数类型中相同的所有函数指针。

我想在c++中实现我的可变类型,其中许多类型检查和继承默认行为是由编译器为我完成的。有安全的方法吗?

PS -我知道我可以创建一个包装器对象并使用new来分配一个新对象,但是我不能为堆栈上的每个int变量增加额外的分配开销。

PPS—代码目前需要在Linux, Mac, iOS和Windows上可移植,但如果有人有一个标准的c++解决方案,那就更好了。

PPPS -类型列表是可扩展的,但在编译时预先确定。我的语言的基础层只定义了基本类型,但是我的语言被编译到的宿主应用程序添加了更多的类型。

使用例子:

CppVariant someNum(42); // Creates it as CppVariantInt.
cout << "Original int: " << someNum->GetAsInt()
    << " (" << someNum->GetAsDouble() << ")" << endl;
someNum->SetAsInt(700); // This is just a setter call.
cout << "Changed int: " << someNum->GetAsInt()
    << " (" << someNum->GetAsDouble() << ")" << endl;
someNum->SetAsDouble(12.34); // This calls destructor on CppVariantInt and constructor on CppVariantDouble(12.34).
cout << "Converted to Double: " << someNum->GetAsInt()
    << " (" << someNum->GetAsDouble() << ")" << endl; // GetAsInt() on a CppVariantDouble() rounds, or whatever.

(想象一下,除了double和int,将来还会有其他类型,比如字符串或布尔值,但是GetAsInt()/SetAsInt()的调用者不应该知道它被存储为什么,只要它可以在运行时转换)

这是一个基于类型擦除、联合和模板特化的解决方案。
我不确定它是否符合你的要求。无论如何,它得到的结果如下:

  • 动态存储
  • 不需要层次结构

您可以很容易地进一步改进它以减少代码量,但这旨在作为开始的基点。

它遵循一个基于问题预期用途的最小的工作示例:

#include<iostream>
class CppVariant {
    union var {
        var(): i{0} {}
        int i;
        double d;
    };
    using AsIntF = int(*)(var);
    using AsDoubleF = double(*)(var);
    template<typename From, typename To>
    static To protoAs(var);
public:
    CppVariant(int);
    CppVariant(double);
    int getAsInt();
    double getAsDouble();
    void setAsInt(int);
    void setAsDouble(double);
private:
    var data;
    AsIntF asInt;
    AsDoubleF asDouble;
 };
template<>
int CppVariant::protoAs<int, int>(var data) {
    return data.i;
}
template<>
int CppVariant::protoAs<double, int>(var data) {
    return int(data.d);
}
template<>
double CppVariant::protoAs<int, double>(var data) {
    return double(data.i);
}
template<>
double CppVariant::protoAs<double, double>(var data) {
    return data.d;
}
CppVariant::CppVariant(int i)
    : data{},
      asInt{&protoAs<int, int>},
      asDouble{&protoAs<int, double>}
{ data.i = i; }
CppVariant::CppVariant(double d)
    : data{},
      asInt{&protoAs<double, int>},
      asDouble{&protoAs<double, double>}
{ data.d = d; }
int CppVariant::getAsInt() { return asInt(data); }
double CppVariant::getAsDouble() { return asDouble(data); }
void CppVariant::setAsInt(int i) {
    data.i = i;
    asInt = &protoAs<int, int>;
    asDouble = &protoAs<int, double>;
}
void CppVariant::setAsDouble(double d) {
    data.d = d;
    asInt = &protoAs<double, int>;
    asDouble = &protoAs<double, double>;
}
int main() {
    CppVariant someNum(42);
    std::cout << "Original int: " << someNum.getAsInt() << " (" << someNum.getAsDouble() << ")" << std::endl;
    someNum.setAsInt(700);
    std::cout << "Changed int: " << someNum.getAsInt() << " (" << someNum.getAsDouble() << ")" << std::endl;
    someNum.setAsDouble(12.34);
    std::cout << "Converted to Double: " << someNum.getAsInt() << " (" << someNum.getAsDouble() << ")" << std::endl;
}

在一个玩笑,我尝试使用放置新的来做到这一点,我有…某物它编译,它做的工作,但我不确定如果它是纯C的改进,因为我不能有c++对象的联合,我创建了一个CPPVMAX()宏传递所有子类中最大的sizeof()作为mBuf[]的大小,但这也不是真的漂亮。

#include <iostream>
#include <string>
#include <cmath>
#define CPPVMAX2(a,b)       (((a) > (b)) ? (a) : (b))
#define CPPVMAX3(a,b,c)     CPPVMAX2((a),CPPVMAX2((b),(c)))
using namespace std;

class CppVariantBase
{
public:
    CppVariantBase() { cout << "CppVariantBase constructor." << endl;  }
    virtual ~CppVariantBase() { cout << "CppVariantBase destructor." << endl; }
    virtual int     GetAsInt() = 0;
    virtual double  GetAsDouble() = 0;
    virtual void    SetAsInt( int n );
    virtual void    SetAsDouble( double n );
};

class CppVariantInt : public CppVariantBase
{
public:
    CppVariantInt( int n = 0 ) : mInt(n)
    {
        cout << "CppVariantInt constructor." << endl;
    }
    ~CppVariantInt() { cout << "CppVariantInt destructor." << endl; }
    virtual int     GetAsInt()      { return mInt; }
    virtual double  GetAsDouble()   { return mInt; }
    virtual void    SetAsInt( int n )           { mInt = n; }
protected:
    int     mInt;
};

class CppVariantDouble : public CppVariantBase
{
public:
    CppVariantDouble( double n = 0 ) : mDouble(n)
    {
        cout << "CppVariantDouble constructor." << endl;
    }
    ~CppVariantDouble()
    {
        cout << "CppVariantDouble destructor." << endl;
    }
    virtual int     GetAsInt()
    {
        if( int(mDouble) == mDouble )
            return mDouble;
        else
            return round(mDouble);
    }
    virtual double  GetAsDouble()           { return mDouble; }
    virtual void    SetAsDouble( int n )    { mDouble = n; }
protected:
    double mDouble;
};

class CppVariant
{
public:
    CppVariant( int n = 0 ) { new (mBuf) CppVariantInt(n); }
    ~CppVariant()           { ((CppVariantBase*)mBuf)->~CppVariantBase(); }
    operator CppVariantBase* () { return (CppVariantBase*)mBuf; }
    CppVariantBase* operator -> () { return (CppVariantBase*)mBuf; }
protected:
    uint8_t mBuf[CPPVMAX3(sizeof(CppVariantBase),sizeof(CppVariantInt),sizeof(CppVariantDouble))];
};
void    CppVariantBase::SetAsInt( int n )
{
    this->~CppVariantBase();
    new (this) CppVariantInt(n);
}

void    CppVariantBase::SetAsDouble( double n )
{
    this->~CppVariantBase();
    new (this) CppVariantDouble(n);
}

int main(int argc, const char * argv[]) {
    CppVariant someNum(42);
    cout << "Original int: " << someNum->GetAsInt()
        << " (" << someNum->GetAsDouble() << ")" << endl;
    someNum->SetAsInt(700); // This is just a setter call.
    cout << "Changed int: " << someNum->GetAsInt()
        << " (" << someNum->GetAsDouble() << ")" << endl;
    someNum->SetAsDouble(12.34);    // This changes the class to CppVariantDouble.
    cout << "Converted to Double: " << someNum->GetAsInt()
        << " (" << someNum->GetAsDouble() << ")" << endl;
    return 0;
}

相关内容

  • 没有找到相关文章

最新更新