用于设置相关类属性的映射的合理键类型?

时间:2012-09-03 20:21:49

标签: c++

我打算编写一个代码,其代码具有如下所示的继承关系,并具有与材质类型相关联的各种属性:

  1. 抽象基类Foo。没有与之相关的属性。
  2. Foo1GeneralElastic继承自Foo类,并具有与可能的各向异性弹性材料相关联的属性。
  3. Foo2GeneralElastic也继承自Foo类,与Foo1GeneralElastic具有相同类型的材质属性,但不同之处。
  4. Foo1PiezoElastic继承自Foo1GeneralElastic,具有压电属性和通用弹性属性。
  5. Foo1IsotropicElastic继承自Foo1GeneralElastic,但不会共享其属性。
  6. 我决定抽象基类有一个或多个方法,它们采用MyPropMap类型的映射,定义为:

    typedef std::map<PropertyLabel,std::vector<double> > MyPropMap
    

    对于PropertyLabel类型,我有几个不同的选择,我试图权衡每个类型的优缺点:

    让PropertyLabel成为enum :这将是轻量级的,但它基本上是一包标签,用于我正在考虑的每种材料的所有不同属性。< / p>

    让PropertyLabel只是int :在这里,我为每种材质类型都有单独的头文件,每种材质都包含静态整数常量的定义对于相关的材料属性。例如,MatPropKeyGenElastic.hpp将定义整数常量ELASTICITY_MATRIXMatPropKeyIsotropicElastic.hpp将定义常量ELASTIC_MODULUSPOISSONS_RATIOMatPropKeyPiezoElastic.hpp将定义#include文件MatPropKeyGenElastic.hpp并另外定义常量PIEZO_CONST_MATRIX

    最棘手的事情是确保没有一个可以一起使用的常量具有相同的值。这可以通过使用脚本生成头文件来实现,该脚本将这些常量的值设置为唯一值。

    让PropertyLabel成为std::string 从这里我可以采取一些不同的方式。我可以在代码中使用像"ELASTICITY_MATRIX"这样的字符串文字,并依赖这些文字永远不会拼错 - 这是一个错误,它会在运行时而不是编译时捕获。我可以按照与上面的方案类似的方式为整数常量定义字符串常量,保持常量唯一的任务是微不足道的:只需将ELASTICITY_MATRIX的值设置为"ELASTICITY_MATRIX",即{POISSONS_RATIO的值1}}到"POISSONS_RATIO"等等。

    除了额外的开销之外,我看到的问题是,我看到了与非POD的全局静态常量相关的恐怖故事,例如主题non-integral constants和{{}中的评论中的那些故事。 {3}}。我想我可以让全局静态常量为const char[]个数组,它们是用作地图键时隐式转换为std::string的POD(和,我不打算让地图键本身为const char*)。我也可以使用预处理器定义字符串文字,但是我无法将它们保存在命名空间中。

    您会推荐上述任何一种方法吗?是否有隐藏的陷阱,我没有注意到?还有其他方法可以推荐吗?

3 个答案:

答案 0 :(得分:1)

我不建议使用字符串。这样简单的任务太昂贵了。我投票给enum。

但是如果将所有标签常量保存在一个地方看起来太难看了,你可以详细说明更复杂的方法 - 使用像两个数字一样的复合键 - (类ID,属性ID)。

两者都可以定义为枚举,也可以嵌套。此外,可以自动生成类ID - 例如在reinterpret_cast指针上使用std::type_info或仅使用std::type_info指针或std::type_index(如果支持)。用代码说明想法:

// PropertyLabel type, could be used as associative container key
struct PropertyLabel: std::pair<const std::type_info*, int>
{
  // Template ctor allows implicit conversion from enums
  // (actually not only from enums but from any int-compatible types)
  // Uncomment explicit keyword if implicit conversions scares you and use
  // explicit conversion syntax - PropertyLabel(smth).
  template <typename T> /*explicit*/ PropertyLabel(T label):
     std::pair<const std::type_info*, int>(&typeid(T), label)
  {
  }
};

// First property holder 
class PropertyUser1
{
public:
  enum Labels
  {
     eProperty1,
     eProperty2,
     eProperty3,
  };
 };

// Second property holder 
class PropertyUser2
{
public:
  enum Labels
  {
     eProperty1,// Due to class scope you could use same names for different properties
     eProperty2,
     eProperty3,
  };
 };

// Usage. A bit dangerous due to implicit conversions, but intuitive and handy:
MyPropMap properties;
properties[PropertyUser1::eProperty1].push_back(42.0);
properties[PropertyUser2::eProperty1].push_back(42.42);
// Will be with explicit ctor:
// properties[PropertyLabel(PropertyUser1::eProperty1)].push_back(42.0);
// properties[PropertyLabel(PropertyUser2::eProperty1)].push_back(42.42);

看起来可以通过更多类型安全性来改进它,从而消除使用非枚举类型(如int)的可能性,例如禁用PropertyLabel(42)等来电。但这只是为了说明想法。

答案 1 :(得分:0)

我刚刚意识到一个相对简单的解决方案,它可以给我很多我想要的东西而不用太大惊小怪。对于MyPropMap类型的任何特定实例,我正在处理一种特定种类材料的属性:各向同性弹性,压电,各向异性弹性等。鉴于此,我可以将每个材质类型对应的枚举包装在自己的命名空间中,并将它们放在相应的头文件中,例如,

// MatPropKey/IsotropicElastic.hpp:
namespace IsotropicElastic {
   enum { ELASTIC_MODULUS, POISSONS_RATIO };
}

// MatPropKey/GenElastic.hpp
namespace GenElastic {
   enum { ELASTICITY_MATRIX }
}

// MatPropKey/PiezoElastic.hpp
namespace PiezoElastic {
   enum { PIEZO_CONST_MATRIX, ELASTICITY_MATRIX }
}

这里有一些冗余,但我可以忍受。只要我坚持上述约定,那么在每个命名空间内,enum值是唯一的,只要我在{{1}的每个实例的特定命名空间中仅使用enum值无论如何我想做什么---我很好。 (实际上,我还希望将每个命名空间包装在一个公共MyPropMap命名空间中。)当然,这不是万无一失的。例如,一个充满创意的傻瓜可以决定MPKey #includeGenElastic.hpp,然后将PiezoElastic.hppGenElastic::ELASTICITY_MATRIX一起使用。然后可能会发生坏事。尽管如此,代码还是传达了如何对命名常量进行分组,避免不必要的名称冲突是微不足道的。

希望我早点想到它。

答案 2 :(得分:0)

经过一番思考,我意识到了一些事情:

  1. 最好将地图包装在一个类中,这样我就可以更好地控制它的编写方式。
  2. 即使是包装的地图也是通用的,并且必须能够容纳任何材料参数类型,因此我只能提供这么多的编译类型安全性。
  3. 鉴于此,我决定设计一个MatProp类,大致如下:

    #include <vector>
    #include <map>
    
    class MatProp {
    public:
       // Skipping the constructor details ...
    
       void setProp_Raw(int propId, double val);
       void getProp_Raw(int propId, double & val) const;
    
       void setProp_Raw(int propId, const std::vector<double> & vals);
       void getProp_Raw(int propId, std::vector<double> & vals) const;
    
       // More overloaded set/get funcs for complex scalars and vectors ...
    
    private:
       // The typedef allows me to write MatPropMap_::iterator, etc. in the
       // implementation of the member functions, which is handy if, say, 
       // I want to swap the std::map for an unordered_map later on.
       typedef std::map<PropertyLabel,std::vector<double> > MatPropMap_;
    
       MatPropMap_ matPropMap_;
    
    };
    

    set / get函数后缀为_Raw,因为很容易将属性ID和值放在一个错误的组合中。我可以将信息传递给MatProp的构造函数,以便可以在运行时验证这些函数的输入,但是设置它可能变得笨拙并使类更难使用。为了增加一些额外的安全性,我可以这样做,例如:

    void setIsotropicLinearElasticParameter(MatProps mProp,
              ElasPropEnum propId, // ELASTIC_MODULUS and POISSONS_RATIO are the 
                                   // *only* valid values of this parameter.
              double val) {
        mProp.setParam_Raw(propId, val);
    }
    

    函数很简单,但我明确地声明(1)只允许两个键,(2)它们应该是double类型。界面并非完全万无一失,但它正确使用相当容易,并且需要花费一些精力才能使用错误。 FWIW,类似的事情在这里完成:http://blog.knatten.org/2010/04/23/make-apis-hard-to-use-incorrectly/