我打算编写一个代码,其代码具有如下所示的继承关系,并具有与材质类型相关联的各种属性:
Foo
。没有与之相关的属性。Foo1GeneralElastic
继承自Foo类,并具有与可能的各向异性弹性材料相关联的属性。Foo2GeneralElastic
也继承自Foo类,与Foo1GeneralElastic
具有相同类型的材质属性,但不同之处。Foo1PiezoElastic
继承自Foo1GeneralElastic
,具有压电属性和通用弹性属性。Foo1IsotropicElastic
继承自Foo1GeneralElastic
,但不会共享其属性。我决定抽象基类有一个或多个方法,它们采用MyPropMap
类型的映射,定义为:
typedef std::map<PropertyLabel,std::vector<double> > MyPropMap
对于PropertyLabel类型,我有几个不同的选择,我试图权衡每个类型的优缺点:
让PropertyLabel成为enum
:这将是轻量级的,但它基本上是一包标签,用于我正在考虑的每种材料的所有不同属性。< / p>
让PropertyLabel只是int
:在这里,我为每种材质类型都有单独的头文件,每种材质都包含静态整数常量的定义对于相关的材料属性。例如,MatPropKeyGenElastic.hpp
将定义整数常量ELASTICITY_MATRIX
,MatPropKeyIsotropicElastic.hpp
将定义常量ELASTIC_MODULUS
和POISSONS_RATIO
,MatPropKeyPiezoElastic.hpp
将定义#include
文件MatPropKeyGenElastic.hpp
并另外定义常量PIEZO_CONST_MATRIX
。
最棘手的事情是确保没有一个可以一起使用的常量具有相同的值。这可以通过使用脚本生成头文件来实现,该脚本将这些常量的值设置为唯一值。
让PropertyLabel成为std::string
从这里我可以采取一些不同的方式。我可以在代码中使用像"ELASTICITY_MATRIX"
这样的字符串文字,并依赖这些文字永远不会拼错 - 这是一个错误,它会在运行时而不是编译时捕获。我可以按照与上面的方案类似的方式为整数常量定义字符串常量,保持常量唯一的任务是微不足道的:只需将ELASTICITY_MATRIX
的值设置为"ELASTICITY_MATRIX"
,即{POISSONS_RATIO
的值1}}到"POISSONS_RATIO"
等等。
const char[]
个数组,它们是用作地图键时隐式转换为std::string
的POD(和否,我不打算让地图键本身为const char*
)。我也可以使用预处理器定义字符串文字,但是我无法将它们保存在命名空间中。
您会推荐上述任何一种方法吗?是否有隐藏的陷阱,我没有注意到?还有其他方法可以推荐吗?
答案 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
#include
和GenElastic.hpp
,然后将PiezoElastic.hpp
与GenElastic::ELASTICITY_MATRIX
一起使用。然后可能会发生坏事。尽管如此,代码还是传达了如何对命名常量进行分组,避免不必要的名称冲突是微不足道的。
答案 2 :(得分:0)
鉴于此,我决定设计一个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/。