C ++ typedef与unlaborated继承

时间:2012-11-26 07:21:05

标签: c++ stl

我有一个由嵌套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容器不打算用作基类,在这种情况下是否存在任何危险?

3 个答案:

答案 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”的争议点之一。

必须考虑两个方面:

  • typedef引入了同一类型的另一个名称: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函数导致调用你的方法?

  • 如果是,继承不是要走的路。 std :: map中没有虚方法,因此运行时多态不起作用。
  • 如果不是,那就是:你只是编写自己的类重用std :: map行为和接口,而无意交换它们的用法(特别是你没有分配你的使用new和deletinf使用删除应用于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;

第二个样本与第一个样本有同样的错误,它只是不太常见:它们都假装使用指向部分对象的指针来操作整个部分对象,部分对象中没有任何东西让你能够知道什么“含一个”是。