C++ C++;typedef与非人工继承

C++ C++;typedef与非人工继承,c++,stl,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::

我有一个由嵌套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;
typedef std::map SmValueProb;
typedef std::map SmGuard;
typedef std::map SmTransitions;
typedef std::map 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> { };
typedef std::map SmValueProb;
类SmGuard:public std::map{};
类SmTransitions:public std::map{};
类SmMachine:public std::map{};
认识到STL容器不打算用作基类,在这种情况下是否存在任何危险?

是的,存在:

std::map<Solver::VariableReference, Solver::EnumValue>* x = new SmGuard;
delete x;
std::map*x=新SmGuard;
删除x;

导致未定义的行为。

有一个危险:如果在指向没有
virtual
析构函数的基类的指针上调用
delete
,则存在未定义的行为。否则,你就没事了

至少这是理论。实际上,在MSVC ABI或安腾ABI(gcc、Clang、icc,…)中,只有当派生类使用非平凡析构函数添加非静态属性时,才会在没有虚拟析构函数的基类上删除(
-Wdelete non-virtual dtor
带有gcc和Clang,前提是该类具有虚拟方法)出现问题(例如a
std::string

在你的特殊情况下,这似乎很好…但是


……您可能仍然想封装(使用组合)并公开有意义的(面向业务的)方法。它不仅比不安全,更容易理解,比<代码> > ->第二。查找(“x”)->开始()/代码>…/P> < P>这是C++与“基于继承的经典OOP”的争议点之一。 必须考虑两个方面:

  • typedef是为同一类型引入另一个名称:
    std::map
    SmValueProb
    实际上是完全相同的东西,可以互换使用
  • 一个引入了一种新的类型,它(原则上)与任何其他类型无关
类关系是通过类的“组合”方式来定义的,它允许隐式操作和转换与其他类型一起实现

除了特定的编程范式(比如OOP,它与“固有”和“是一种”关系的概念相关联)继承、隐式构造函数、隐式强制转换等等,所有这些都做同样的事情:让一个类型跨另一个类型的接口使用,从而定义一个跨不同类型的可能操作网络(一般而言)“多态性”

关于如何构造这样一个网络,存在着各种各样的编程范例,每种编程范例都试图优化编程的一个特定方面,例如表示或运行时可重放对象(经典OOP)、表示编译时可重放对象(CRTP)、对不同类型使用genreric算法函数(通用编程),使用“纯函数”表示算法组成(函数和lambda“捕获”)

他们都规定了一些“规则”,关于语言“特征”必须如何使用,因为C++是多范式的——非它的特征满足了范式的要求,让一些污秽打开。 如上所述,继承std::map不会产生纯OOP可替换类型,因为基指针上的删除将不知道如何销毁派生部分,因为析构函数在设计上不是虚拟的

但是-事实上-这只是一个特殊情况:而且
pbase->find
不会调用您自己的最终被覆盖的
find
方法,因为
std::map::find
不是虚拟的(但这并不是未定义的:定义很好,很可能不是您想要的)

真正的问题是另一个:“经典OOP替换原则”在您的设计中是否重要? 换句话说,您是否要互换使用类及其基,函数只取
std::map*
std::map&
参数,假装这些函数调用std::map函数,从而调用您的方法

  • 如果是的话,继承就不是办法。std::map中没有虚拟方法,因此运行时多态性将不起作用
  • 如果没有,也就是说:您只是编写自己的类,重用std::map行为和接口,不打算交换它们的用法(特别是,您没有使用new和deletin分配自己的类,如果将delete应用于std::map指针),只提供一组以
    yourclass&
    yourclass*
    为参数的函数,这很好。它甚至可能比typedef更好,因为您的函数不能再与std::map一起使用,从而分离了功能
替代方案可以是“封装”:也就是说:使map和类的显式成员可以作为公共成员访问map,或使其成为具有访问器函数的私有成员,或重写类中的map接口。最终,您将获得一个具有相同接口的不相关类型,这是它自己的行为。代价是重写某个类的整个接口这可能有百分之一百的方法

注: 对于任何考虑到虚拟dtor丢失危险的人,请注意,在公众视线范围内封装tat并不能解决问题:

class myclass: public std::map<something...>
{};

std::map<something...>* p = new myclass;
delete p;
class myclass:public std::map
{};
std::map*p=新的myclass;
删除p;
UB很像吗

class myclass
{
public:
   std::map<something...> mp;
};

std::map<something...>* p = &((new myclass)->mp);
delete p;
class-myclass
{
公众:
std::map-mp;
};
std::map*p=&((新myclass)->mp);
删除p;
第二个样本具有相同的特征