如何更改 C++ 对象的 class(实现可变类型)
How change class of a C++ object (implementing a variadic type)
首先:我知道更改对象的 class 通常不是一个好主意,但我正在实现我自己的编程语言,并且它具有可以包含值的变量任何类型,甚至随意改变它们的类型,所以请假设我不是初学者,不了解 OO 基础知识。
目前,我在 C 中实现了变体变量。每个变量都有一个指向 table 函数指针的指针,包含 SetAsInt()
、SetAsString()
等函数,其次是C ++中的实例变量是什么。所有对象的大小都相同。
当一个变量包含一个字符串并且有人为它分配一个 Int 时,我手动调用析构函数,将函数指针的 table 更改为指向用于可变 int 值的 table,然后 然后 设置它的 int 实例变量。
这有点难以维护,因为每次添加新类型时,我都必须添加一个新的table函数指针并填写all其中的函数指针。函数指针结构的类型检查似乎很糟糕,缺少字段不会导致投诉,所以我很容易不小心忘记列表中的一个指针并导致有趣的崩溃。另外,我必须重复所有在大多数类型中都相同的函数指针。
我想改为在 C++ 中实现我的可变参数类型,其中很多类型检查和继承默认行为都是由编译器为我完成的。有安全的方法吗?
PS - 我知道我可以创建一个包装器对象并使用 new
分配一个新对象,但我不能为每个对象增加额外的分配开销int
堆栈上的变量。
PPS - 代码需要在 Linux、Mac、iOS 和 [=50 之间 table =] 现在,但如果有人有标准的 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() 的调用者不必知道它存储为什么,只要能在运行时转换即可)
幸运的是,我尝试使用 placement new 来做到这一点,我有...一些东西...它编译,它完成了工作,但我是不确定它是否是对纯 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;
}
这是一个基于类型擦除、联合和模板特化的解决方案。
我不确定它是否符合您的要求。
无论如何,这是它得到的:
- 任何东西都放在动态存储中
- 不需要层次结构
您可以轻松地进一步改进它以减少代码量,但这旨在作为一个起点。
它遵循一个基于问题预期用途的最小工作示例:
#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;
}
首先:我知道更改对象的 class 通常不是一个好主意,但我正在实现我自己的编程语言,并且它具有可以包含值的变量任何类型,甚至随意改变它们的类型,所以请假设我不是初学者,不了解 OO 基础知识。
目前,我在 C 中实现了变体变量。每个变量都有一个指向 table 函数指针的指针,包含 SetAsInt()
、SetAsString()
等函数,其次是C ++中的实例变量是什么。所有对象的大小都相同。
当一个变量包含一个字符串并且有人为它分配一个 Int 时,我手动调用析构函数,将函数指针的 table 更改为指向用于可变 int 值的 table,然后 然后 设置它的 int 实例变量。
这有点难以维护,因为每次添加新类型时,我都必须添加一个新的table函数指针并填写all其中的函数指针。函数指针结构的类型检查似乎很糟糕,缺少字段不会导致投诉,所以我很容易不小心忘记列表中的一个指针并导致有趣的崩溃。另外,我必须重复所有在大多数类型中都相同的函数指针。
我想改为在 C++ 中实现我的可变参数类型,其中很多类型检查和继承默认行为都是由编译器为我完成的。有安全的方法吗?
PS - 我知道我可以创建一个包装器对象并使用 new
分配一个新对象,但我不能为每个对象增加额外的分配开销int
堆栈上的变量。
PPS - 代码需要在 Linux、Mac、iOS 和 [=50 之间 table =] 现在,但如果有人有标准的 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() 的调用者不必知道它存储为什么,只要能在运行时转换即可)
幸运的是,我尝试使用 placement new 来做到这一点,我有...一些东西...它编译,它完成了工作,但我是不确定它是否是对纯 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;
}
这是一个基于类型擦除、联合和模板特化的解决方案。
我不确定它是否符合您的要求。
无论如何,这是它得到的:
- 任何东西都放在动态存储中
- 不需要层次结构
您可以轻松地进一步改进它以减少代码量,但这旨在作为一个起点。
它遵循一个基于问题预期用途的最小工作示例:
#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;
}