1

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

  1. 抽象基类Foo。没有与之相关的属性。
  2. Foo1GeneralElastic继承自类 Foo 并具有与可能的各向异性弹性材料相关的属性。
  3. Foo2GeneralElastic也继承自类 Foo 并具有与 相同种类的材料属性Foo1GeneralElastic,但在其他方面有所不同。
  4. Foo1PiezoElastic继承自Foo1GeneralElastic并具有压电特性以及通用弹性特性。
  5. Foo1IsotropicElastic继承自Foo1GeneralElastic,但不共享其属性。

我决定抽象基类将具有一个或多个采用类型映射的方法MyPropMap,定义为:

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

关于 PropertyLabel 类型可能是什么,我有几个不同的选项,我正在尝试权衡每种选项的优缺点:

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

让 PropertyLabel 只是一个int: 在这里,我将为每种材料类型提供单独的头文件,每个头文件都包含静态整数常量的定义,这些常量将是相关材料属性的标签。例如,MatPropKeyGenElastic.hpp将定义整数常量ELASTICITY_MATRIXMatPropKeyIsotropicElastic.hpp将定义常量ELASTIC_MODULUSPOISSONS_RATIO,并将MatPropKeyPiezoElastic.hpp文件#includeMatPropKeyGenElastic.hpp另外定义常量PIEZO_CONST_MATRIX

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

让 PropertyLabel 成为一个std::string从这里我可以采取几种不同的方式。我可以只"ELASTICITY_MATRIX"在代码中使用字符串文字,并依赖这些文字永远不会拼写错误——一个会在运行时而不是编译时捕获的错误。我可以用类似于上述整数常量方案的方式定义字符串常量,并且保持常量唯一的任务将是微不足道的:只需设置to的值、 ELASTICITY_MATRIXto"ELASTICITY_MATRIX"的值等。POISSONS_RATIO"POISSONS_RATIO"

除了额外的开销之外,我看到的问题是我看到了与非 POD 的全局静态常量相关的恐怖故事,例如非整数常量定义类字符串常量主题中的评论中的那些C++?. 我想我可以让全局静态常量是数组,它们是当用作映射键时const char[]会隐式转换为 s 的 POD (并且,,我不打算让映射键本身成为)。我还可以使用预处理器定义字符串文字,但是我无法将它们保存在命名空间中。std::stringconst char*

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

4

3 回答 3

1

我不建议使用字符串。对于这么简单的任务来说太贵了。我投票给枚举。

但是,如果将所有标签常量放在一个地方看起来太难看,您可以制定更复杂的方法 - 使用像两个数字对的复合键 - (类 ID,属性 ID)。

两者都可以定义为枚举,也可以是嵌套的。此外,类 ID 可以自动生成 - 例如使用reinterpret_castonstd::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)。但这只是为了说明想法。

于 2012-09-03T23:00:59.310 回答
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值都是唯一的,并且只要我只enum为每个实例使用特定命名空间中的值MyPropMap---无论如何我都想做---我没事。(实际上,我还想将这些命名空间中的每一个都包装在一个公共MPKey命名空间中。)当然,这并不是万无一失的。例如,一个足够有创意的傻瓜可能会决定#include两者都使用GenElastic.hppPiezoElastic.hpp然后GenElastic::ELASTICITY_MATRIXPiezoElastic::PIEZO_CONST_MATRIX. 坏事可能会发生。尽管如此,代码还是传达了命名常量应该如何分组,避免不必要的名称冲突是微不足道的。

希望我早点想到它。

于 2012-09-04T21:55:48.537 回答
0

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

  1. 最好将地图包装在一个类中,这样我就可以更好地控制它的编写方式。
  2. 甚至包装的地图也是通用的,并且必须能够适应任何材质参数类型,因此我只能提供这么多的编译类型安全性。

鉴于此,我决定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)它们确实应该是 type double。该界面并非完全万无一失,但正确使用它相当容易,并且需要一些努力才能使用错误。FWIW,在这里做了类似的事情:http ://blog.knatten.org/2010/04/23/make-apis-hard-to-use-incorrectly/ 。

于 2012-09-06T02:17:26.987 回答