Class 具有一般和特殊属性
Class with general and specialized attributes
如何使用基于类型的一般属性和特定属性为 class 建模。例如我有一个 Primitive class。 Primitive class 具有以下一般成员:PrimitiveType、translation、rotation,以及基于原始类型的附加字段。
enum PrimitiveType
{
CYLYNDER,
CUBE,
CONE
}
class Primitive
{
string name;
PrimitiveType type;
double positionX,positionY,positionZ;
double rotationX,rotationY,rotationZ;
// following members are if primitivetype is CYLYNDER
double height;
double radius;
//following members are if primitive is CUBE
double height;
double width;
double length;
};
我当然可以继承并制作继承自 Primitive 的 Primitive 和 Cylynder 和 Cube classes。但是 classes 之间没有多态关系,所以我不使用继承。我需要它们作为保存属性的普通结构。
我也可以制作合成并制作具有 Primitive 成员的 Cylynder 和 Cube classes。但我需要将 Cylynder、Cube 和 Cone 的对象存储在一个向量中。因此,如果我进行组合,我将如何将它们存储在一个 std::vector .
中
我基本上需要以满足以下要求的方式对结构进行建模:
1) 将不同类型组件的对象存储在一个std::vector
2) 将不同类型的对象存储在易于阅读和编辑的配置文件中。
在配置文件中,我只想保存与特定基元类型相关的专用属性,而不是所有基元类型的专用属性。
所以我想在配置文件中得到这样的东西:
<Primitive>
<name>Primitive1</name>
<type>CYLYNDER</type>
<positionx>0</positionx>
<!-- other common attributes here, omitted to save space -->
<!-- specific primitive type attributes -->
<height> 10 </height>
<redius>5</radius>
</Primitive>
<Primitive>
<name> Primitive2 </name>
<type> CUBE </type>
<positionx>0</positionx>
<!-- other common attributes here, omitted to save space -->
<!-- specific primitive type attributes -->
<height>10</height>
<width>10</width>
<length>10</length>
</Primitive>
您有很多选择,例如:
创建三个不同的 classes 并将 boost::variant<Cylinder, Cube, Cone>
s 存储在 vector
中(概念上这是一个有区别的联合,但 boost::variant
清理它为你处理像对齐这样丑陋的边缘情况)
- 如果需要,您仍然可以为共享 members/functionality 使用合成
创建一个 class 带有一个枚举和一个 "fat" 界面和字段(正如您在上面开始做的那样,但是将高度的重复项删除到 "/ / 对于许多原语”组)
- 棘手和纠结,但如果您没有太多要处理的 PrimitiveTypes,并且它们不是太复杂,这可能是易于管理和实用的
使用多态性——看起来肯定会有一些可以公开的通用功能子集,虽然它很丑 dynamic_cast<>
支持特定于运行时类型的切换
- 您的 "have not polymorphic relationships between themselves, so I do not to use inheritance" 并不是避免它的令人信服的理由,因为您声称需要将类型存储在单个
vector
至于从文件内容创建对象...你需要的是 "factory" - 它可以从配置文件中读取 PrimitiveType,然后调用特定类型的字段解析,最后调用构造代码.
这对你的情况来说可能有点矫枉过正,但我习惯于在专有 API 中处理这类问题,这些问题无法使用 boost::variant
之类的方法来避免 public依赖性(要求插件编写者安装 boost)并允许变体类型在伪运行时扩展支持类型的范围(例如:来自在应用程序中间加载的 dylib 插件)。有一个通用的构建块可以将非同质类型变成同质的东西,能够存储在单个容器中,统一访问等。
当您需要那些类型的运行时 属性 系统和反射以及类型之间某种程度的同质性时,C++ 的静态类型特性可能会使它变得有点困难。该语言不会为您提供必要的方式来完成这些类型的事情。如果您有严格的 ABI 限制,这尤其棘手。
如果您对这种卷起袖子的专有解决方案感兴趣,第一个构建块是跨模块边界工作的穷人 RTTI:
// Implemented at a central core level.
int to_type_code(const char* type_name);
// Registers a new type with the system.
// Thread safety omitted here for the sake of simplicity.
#define REGISTER_TYPE(type) \
template <> inline \
int type_code<type>() \
{ \
static const int val = to_type_code(#type); \
return val; \
}
// Example usage:
REGISTER_TYPE(bool);
REGISTER_TYPE(char);
REGISTER_TYPE(short);
REGISTER_TYPE(int);
// etc
现在您可以从任何模块执行 type_code<int>()
之类的操作并获得相同的结果。这为我们提供了一些我们可以在其上映射特定类型功能的东西,比如当系统遇到给定类型时显示的 GUI control/widget(允许您从数据自动生成 GUI,尤其是与反射结合使用时非常方便) ,需要什么功能来序列化该类型、克隆它、创建它、销毁它、将它转换为另一种类型等等——随心所欲。
下一步是一种通过同类类型来通用存储该类型的方法,类似于 boost::variant
。简化(省略 const
方法,例如)您可以构建的示例:
class Variant
{
public:
virtual ~Variant() {}
virtual int type() const = 0;
virtual void* data() = 0;
// Can/should use a smart pointer here (for ABI, make sure it captures
// destruction at the call site).
virtual Variant* clone() const = 0;
template <class T>
T& get()
{
assert(type_code<T>() != -1 && "Type is unknown!");
assert(type() == type_code<T>() && "Mismatching types!");
return static_cast<T*>(data());
}
template <class T>
Variant& operator=(const T& new_val)
{
assert(type_code<T>() != -1 && "Type is unknown!");
assert(type() == type_code<T>() && "Mismatching types!");
*static_cast<T*>(data()) = new_val;
}
// ...
};
template <class T>
class VariantT: public Variant
{
public:
explicit VariantT(const T& ival): val(ival), code(type_code<T>())
{
assert(code != -1 && "Type is unknown!");
}
virtual VariantT* clone() const override {return new VariantT(*this);}
virtual int type() const override {return type_code<T>();}
virtual void* data() override {return &val;}
private:
T val;
};
现在你可以存储你的基本类型,甚至VariantT中的字段,VariantT中的高度等等,它们都可以通过Variant*
引用。示例:
VariantT<double> var_double(1.5);
Variant& var = var_double;
double my_double = var.get<double>(); // --> 1.5
int my_int = var.get<int>(); // assertion failure -- type mismatch.
unique_ptr<Variant> clone(var.clone()); // make a copy
这里有一些 vptr
开销,但如果您需要这种可扩展的 属性 设计,与仅在聚合对象级别应用这些抽象相比,它需要的工作要少得多。如果您需要更高的安全性,您还可以将那些仅用于调试的断言转换为具有运行时分支的异常或错误处理程序,前提是您能够支付得起这些周期。使用它,您可以提供具有反射的对象(列出可用的变体),通过 Variant* 统一存储它们的属性,将序列化程序映射到适当的类型代码并统一序列化特定 attributes/properties 的整个对象,等等
如何使用基于类型的一般属性和特定属性为 class 建模。例如我有一个 Primitive class。 Primitive class 具有以下一般成员:PrimitiveType、translation、rotation,以及基于原始类型的附加字段。
enum PrimitiveType
{
CYLYNDER,
CUBE,
CONE
}
class Primitive
{
string name;
PrimitiveType type;
double positionX,positionY,positionZ;
double rotationX,rotationY,rotationZ;
// following members are if primitivetype is CYLYNDER
double height;
double radius;
//following members are if primitive is CUBE
double height;
double width;
double length;
};
我当然可以继承并制作继承自 Primitive 的 Primitive 和 Cylynder 和 Cube classes。但是 classes 之间没有多态关系,所以我不使用继承。我需要它们作为保存属性的普通结构。
我也可以制作合成并制作具有 Primitive 成员的 Cylynder 和 Cube classes。但我需要将 Cylynder、Cube 和 Cone 的对象存储在一个向量中。因此,如果我进行组合,我将如何将它们存储在一个 std::vector .
中我基本上需要以满足以下要求的方式对结构进行建模:
1) 将不同类型组件的对象存储在一个std::vector
2) 将不同类型的对象存储在易于阅读和编辑的配置文件中。 在配置文件中,我只想保存与特定基元类型相关的专用属性,而不是所有基元类型的专用属性。 所以我想在配置文件中得到这样的东西:
<Primitive>
<name>Primitive1</name>
<type>CYLYNDER</type>
<positionx>0</positionx>
<!-- other common attributes here, omitted to save space -->
<!-- specific primitive type attributes -->
<height> 10 </height>
<redius>5</radius>
</Primitive>
<Primitive>
<name> Primitive2 </name>
<type> CUBE </type>
<positionx>0</positionx>
<!-- other common attributes here, omitted to save space -->
<!-- specific primitive type attributes -->
<height>10</height>
<width>10</width>
<length>10</length>
</Primitive>
您有很多选择,例如:
创建三个不同的 classes 并将
boost::variant<Cylinder, Cube, Cone>
s 存储在vector
中(概念上这是一个有区别的联合,但boost::variant
清理它为你处理像对齐这样丑陋的边缘情况)- 如果需要,您仍然可以为共享 members/functionality 使用合成
创建一个 class 带有一个枚举和一个 "fat" 界面和字段(正如您在上面开始做的那样,但是将高度的重复项删除到 "/ / 对于许多原语”组)
- 棘手和纠结,但如果您没有太多要处理的 PrimitiveTypes,并且它们不是太复杂,这可能是易于管理和实用的
使用多态性——看起来肯定会有一些可以公开的通用功能子集,虽然它很丑
dynamic_cast<>
支持特定于运行时类型的切换- 您的 "have not polymorphic relationships between themselves, so I do not to use inheritance" 并不是避免它的令人信服的理由,因为您声称需要将类型存储在单个
vector
- 您的 "have not polymorphic relationships between themselves, so I do not to use inheritance" 并不是避免它的令人信服的理由,因为您声称需要将类型存储在单个
至于从文件内容创建对象...你需要的是 "factory" - 它可以从配置文件中读取 PrimitiveType,然后调用特定类型的字段解析,最后调用构造代码.
这对你的情况来说可能有点矫枉过正,但我习惯于在专有 API 中处理这类问题,这些问题无法使用 boost::variant
之类的方法来避免 public依赖性(要求插件编写者安装 boost)并允许变体类型在伪运行时扩展支持类型的范围(例如:来自在应用程序中间加载的 dylib 插件)。有一个通用的构建块可以将非同质类型变成同质的东西,能够存储在单个容器中,统一访问等。
当您需要那些类型的运行时 属性 系统和反射以及类型之间某种程度的同质性时,C++ 的静态类型特性可能会使它变得有点困难。该语言不会为您提供必要的方式来完成这些类型的事情。如果您有严格的 ABI 限制,这尤其棘手。
如果您对这种卷起袖子的专有解决方案感兴趣,第一个构建块是跨模块边界工作的穷人 RTTI:
// Implemented at a central core level.
int to_type_code(const char* type_name);
// Registers a new type with the system.
// Thread safety omitted here for the sake of simplicity.
#define REGISTER_TYPE(type) \
template <> inline \
int type_code<type>() \
{ \
static const int val = to_type_code(#type); \
return val; \
}
// Example usage:
REGISTER_TYPE(bool);
REGISTER_TYPE(char);
REGISTER_TYPE(short);
REGISTER_TYPE(int);
// etc
现在您可以从任何模块执行 type_code<int>()
之类的操作并获得相同的结果。这为我们提供了一些我们可以在其上映射特定类型功能的东西,比如当系统遇到给定类型时显示的 GUI control/widget(允许您从数据自动生成 GUI,尤其是与反射结合使用时非常方便) ,需要什么功能来序列化该类型、克隆它、创建它、销毁它、将它转换为另一种类型等等——随心所欲。
下一步是一种通过同类类型来通用存储该类型的方法,类似于 boost::variant
。简化(省略 const
方法,例如)您可以构建的示例:
class Variant
{
public:
virtual ~Variant() {}
virtual int type() const = 0;
virtual void* data() = 0;
// Can/should use a smart pointer here (for ABI, make sure it captures
// destruction at the call site).
virtual Variant* clone() const = 0;
template <class T>
T& get()
{
assert(type_code<T>() != -1 && "Type is unknown!");
assert(type() == type_code<T>() && "Mismatching types!");
return static_cast<T*>(data());
}
template <class T>
Variant& operator=(const T& new_val)
{
assert(type_code<T>() != -1 && "Type is unknown!");
assert(type() == type_code<T>() && "Mismatching types!");
*static_cast<T*>(data()) = new_val;
}
// ...
};
template <class T>
class VariantT: public Variant
{
public:
explicit VariantT(const T& ival): val(ival), code(type_code<T>())
{
assert(code != -1 && "Type is unknown!");
}
virtual VariantT* clone() const override {return new VariantT(*this);}
virtual int type() const override {return type_code<T>();}
virtual void* data() override {return &val;}
private:
T val;
};
现在你可以存储你的基本类型,甚至VariantT中的字段,VariantT中的高度等等,它们都可以通过Variant*
引用。示例:
VariantT<double> var_double(1.5);
Variant& var = var_double;
double my_double = var.get<double>(); // --> 1.5
int my_int = var.get<int>(); // assertion failure -- type mismatch.
unique_ptr<Variant> clone(var.clone()); // make a copy
这里有一些 vptr
开销,但如果您需要这种可扩展的 属性 设计,与仅在聚合对象级别应用这些抽象相比,它需要的工作要少得多。如果您需要更高的安全性,您还可以将那些仅用于调试的断言转换为具有运行时分支的异常或错误处理程序,前提是您能够支付得起这些周期。使用它,您可以提供具有反射的对象(列出可用的变体),通过 Variant* 统一存储它们的属性,将序列化程序映射到适当的类型代码并统一序列化特定 attributes/properties 的整个对象,等等