我有一个由嵌套STL容器组成的数据结构:
typedef std::map<Solver::EnumValue, double> SmValueProb;
typedef std::map<Solver::VariableReference, Solver::EnumValue> SmGuard;
typedef std::map<SmGuard, SmValueProb> SmTransitions;
typedef std::map<Solver::EnumValue, SmTransitions> SmMachine;
这种形式的数据仅在我的程序中短暂使用,除了简单地存储数据之外,附加到这些类型的行为并不多。但是,编译器(VC ++ 2010)抱怨结果名称太长。
将类型重新定义为STL容器的子类而无需进一步详细说明似乎有效:
typedef std::map<Solver::EnumValue, double> SmValueProb;
class SmGuard : public std::map<Solver::VariableReference, Solver::EnumValue> { };
class SmTransitions : public std::map<SmGuard, SmValueProb> { };
class SmMachine : public std::map<Solver::EnumValue, SmTransitions> { };
认识到STL容器不打算用作基类,在这种情况下是否存在任何危险?
答案 0 :(得分:11)
有一个危险:如果在指向没有delete
析构函数的基类的指针上调用virtual
,则会出现未定义的行为。否则,你很好。
至少这是理论。实际上,在基类上的MSVC ABI或Itanium ABI(gcc,Clang,icc,...)delete
中没有虚拟析构函数(-Wdelete-non-virtual-dtor
带有gcc和clang,提供类有如果派生类使用非平凡的析构函数添加非静态属性(例如a std::string
),则只会导致问题。
在你的具体情况下,这看起来很好......但是......
...您可能仍希望封装(使用Composition)并公开有意义的(面向业务)方法。它不仅危害性更小,而且比it->second.find('x')->begin()
...
答案 1 :(得分:1)
是的,有:
std::map<Solver::VariableReference, Solver::EnumValue>* x = new SmGuard;
delete x;
导致未定义的行为。
答案 2 :(得分:1)
这是C ++与“基于继承的经典OOP”的争议点之一。
必须考虑两个方面:
std::map<Solver::EnumValue, double>
和SmValueProb
-at all effect-完全相同的东西,cna可以互换使用。类关系由类“组成”的方式定义,以及允许其他类型的隐式操作和转换的内容。
在特定编程范例之外(如OOP,与“inhritance”和“is-a”关系的概念相关联)继承,隐式构造函数,隐式强制转换等等,都做同样的事情:让一个类型在另一种类型的接口上使用,从而定义跨不同类型的可能操作的网络。这是(一般来说)“多态”。
关于如何构建这样的网络,每个都试图优化编程的特定方面,如表示或运行时可替换对象(经典OOP),编译时可替换对象(CRTP)的表示,存在各种编程范例。 ,使用genreric算法函数用于不同类型(通用编程),使用“纯函数”来表达算法组合(functional和lambda“capture”)。
所有这些都规定了一些关于必须如何使用语言“特征”的“规则”,因为C ++多范式 - 其特征不仅仅满足范式的要求,而是让一些肮脏的东西开放。
正如Luchian所说,继承std :: map不会产生纯OOP可替换类型,因为删除基指针将不知道如何销毁派生部分,因为析构函数不是虚拟的设计。
但事实上 - 这只是一个特例:同样pbase->find
也不会调用你自己最终被覆盖的find
方法,因为std::map::find
不是虚拟的。 (但这并不是未定义的:很明确的定义很可能不是你想要的)。
真正的问题是另一个问题:“经典的OOP替代原则”在您的设计中是否重要?
换句话说,你是否会互换使用你的类和它们的基础,函数只是取std::map*
或std::map&
参数,假装这些函数调用std :: map函数导致调用你的方法?
yourclass&
或yourclass*
为参数的函数,这非常好。它甚至可能比typedef更好,因为你的函数不能再与std :: map一起使用,从而将功能分开。替代方案可以是“封装”:即:使您的类的地图和显式成员可以将地图作为公共成员访问,或者使其成为具有访问者功能的私有成员,或者重写您自己的地图界面你的班。你最后通过相同的接口获得一个与其自身行为不相关的类型。以重写可能有百分之一方法的东西的整个界面为代价。
对于任何想到虚假人员失踪危险的人来说,注意公共能见度并不能解决问题:
class myclass: public std::map<something...>
{};
std::map<something...>* p = new myclass;
delete p;
UB非常喜欢
class myclass
{
public:
std::map<something...> mp;
};
std::map<something...>* p = &((new myclass)->mp);
delete p;
第二个样本与第一个样本有同样的错误,它只是不太常见:它们都假装使用指向部分对象的指针来操作整个部分对象,部分对象中没有任何东西让你能够知道什么“含一个”是。