出于我的一个项目的目的,我想制作一个可以包含任何类型数据的 typedef struct
。我制作了一个存储在 enum
中的数据类型,以及一个实际存储数据的 void *
。
在我目前的尝试中,它与 int 一起工作得很好,但是当我尝试添加其他类型如 std::string
或 float
时,它工作得不太好.
我已经尝试了几种不同的方法来尝试将数据放入容器并将其取出,但到目前为止没有一种方法适用于所有情况。
最好的方法是什么?我想将任何类型的数据填充到一个结构中,并能够以相同的形式检索数据。
编辑:
typedef struct{
DataTypeInt,
DataTypeFloat,
DataTypeString
}DataType;
typedef struct{
DataType type;
void *data;
}Data;
起初我只用整数进行测试,我会简单地使用 (void *)foo
来输入数据。然后放入(int)bar.data
就可以把数据取出来了。这对 int
类型非常有效,但如果我尝试 std::string
或 float
在摸索了不同的指针和转换配置,并搜索了互联网之后,我最终简单地放置了 foo.data = &bar
来输入数据和 float foo = *(float* )bar.data
来获取数据。这没有给出任何错误,但数据没有正确输出。通过另一侧的任何 int
或 float
都将是 0
并且字符串将是随机符号。
我还尝试了这两种方法,使用包装器结构来包含每种数据类型,然后将其作为数据传递。
最佳答案
1) 如果您需要变体,并且不介意使用 Boost,请考虑使用 Boost.Variant 或 Boost.Any。它干净、健壮且成熟。
2) 使用重载实现非 union 变体。 (真正的 union 的问题是针对非 POD 类型,它丑陋到连经验丰富的开发人员都忘记了如何正确地做)。最好远离。
class Variant
{
int i;
float j;
std::string s;
// Add all of your typed getter and setters
}
通过重载,您可以创建一个非 union 包装类,每个类型都有一个成员,然后为每个类型添加带有覆盖的 getter/setter 方法。只要您不打算创建数百万个这种类型,它就比使用 void *
更安全。
3) 使用子类型多态性 - 指定所有不同可能类型的基类,然后使用特定实现进行扩展。
class VariantBase
{
public:
virtual int get_int() { throw new "Not implemented"; }
virtual float get_float() { throw new "Not implemented"; }
virtual std::string get_string() { throw new "Not implemented"; }
}
class VariantInt
{
int val;
public:
virtual int get_int() { return val; }
virtual void set_int(int i) { val = i; }
}
class VariantString
{
...
}
4) 如果您习惯了 C#,其中所有类型都派生自 System.Object,那么与 C++ 中最接近的是 void *
但是,与 C# 不同的是,您在 void * 中存储的内容会暂时失去其类型,并且您无法使用反射。更重要的是,如果你踩它,垃圾收集器不会救你。因此,与其只希望最好,不如围绕 void *
实现一个安全的 Variant 包装器。保护您免受自己的伤害。
你可以把一个对象放入void *
void * vp = obj;
但要将其输出,您需要使用 C++ 强制转换。
MyClass * obj = static_cast<MyClass*>(vp);
您可以使用 void *
实现安全变体在 C++ 的规则之内。请记住,有 Boost.Variant (1)。我把它放在这里只是为了演示。这是危险的,通常意味着您忽略了其他有效的解决方案。但是,C 程序员经常使用该技术。 C++ 语言的创建者 Bjarne Stroustrup 明确认为这是合法且有值(value)的,我认为它只是 C++ 的一部分。这里的总体思路是一个简单的 3 类型变体。更好的 C++ 开发人员会使用模板来做很多这样的事情,但是由于您是 C++ 的新手,所以它是一种非模板解决方案,我希望您能理解。使用 void *
时在尝试转换指针之前,您必须始终确定指针指的是什么类型,因此一个好的规则是将所有访问器包装在一个验证枚举值的函数中。首先检查您的枚举值,如果不匹配(有人在字符串上调用 get_int)则抛出异常,尽管某些变体支持转换。由于您正在处理指针,因此 set_val_type() 方法将首先删除旧内存。注意 static_cast<>
的使用,如果您想要干净、定义好的代码,这是在 C++ 中转换 void * 的唯一方法。
enum VariantValType { None, Int, Float, StdString };
class Variant
{
VariantValType valType;
void *val;
public:
Variant() : val(nullptr), valType(None) {}
~Variant() { reset_val(); }
void set_val_type(VariantValType t) {
if (t == valType)
return;
reset_val();
valType = t;
}
void reset_val() {
switch (valType) {
case None: break;
case Int: delete (static_cast<int*>(val)); break;
case Float: delete (static_cast<float*>(val)); break;
case StdString: delete (static_cast<std::string*>(val)); break;
default: throw "Unknown value type"; break;
}
valType = None;
val = nullptr;
}
int get_int() {
if (valType != Int)
throw "Variant type mismatch";
return *(static_cast<int*>(val));
}
void set_int(int i) {
set_val_type(Int);
val = new int{ i };
}
float get_float() {
if (valType != Float)
throw "Variant type mismatch";
return *(static_cast<float*>(val));
}
void set_float(float f) {
set_val_type(Float);
val = new float{ f };
}
std::string& get_string() {
// If you like, implement conversion here with std::to_string() like so
// if(valType == Int) return std::to_string(*(static_cast<int*>(val)));
if (valType != StdString)
throw "Variant type mismatch";
return *(static_cast<std::string*>(val));
}
void set_string(const std::string& s) {
set_val_type(StdString);
val = new std::string(s);
}
};
关于c++ - 将任何数据类型传递给 C++ 中的函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26580791/