具有一般和专用属性的类

时间:2015-05-08 07:06:50

标签: c++ design-patterns configuration

如何根据类型为具有常规属性和特定属性的类建模。例如,我有一个原始类。 Primitive类具有以下一般成员: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类。但是类之间没有多态关系,所以我不使用继承。我需要它们只是具有属性的普通结构。

我也可以制作组合并制作具有Primitive成员的Cylynder和Cube类。但我需要在一个向量中存储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>

2 个答案:

答案 0 :(得分:2)

你有很多选择,例如:

  • 创建三个不同的类并在boost::variant<Cylinder, Cube, Cone>中存储vector(概念上这是一个有区别的联合,但是boost::variant为你清理它并处理丑陋的边缘情况,如对准)

    • 如果需要,您仍然可以使用合成作为共享成员/功能
  • 使用枚举和“胖”界面和字段创建一个类(如上所述,但将高度复制删除为“// for many primitives”组)

    • hackish and entangled,但是如果你没有太多的PrimitiveTypes来处理它们并且它们不是太复杂,那么这可能是可管理和实用的
  • 使用多态 - 看起来肯定会有一些常见的功能子集可以公开,虽然它很丑陋dynamic_cast<>支持特定于运行时类型的切换

    • 你的“之间没有多态关系,所以我不使用继承”并不是一个令人信服的理由来避免它,因为你声称需要将类型存储在一个{{ 1}}

至于从文件内容创建对象...你需要什么称为“工厂” - 它可以从配置文件中读取PrimitiveType,然后调用特定于类型的字段解析,最后调用构造代码。

答案 1 :(得分:0)

这可能对您的情况有点过分,但我习惯于在专有API中处理这些问题,这些问题无法使用boost::variant这样的问题来避免公共依赖(需要插件)安装了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控件/小部件(允许您从数据中自动生成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 *统一存储它们的属性,将序列化器映射到适当的类型代码,并统一序列化特定属性/属性的整个对象等。