C++:继承自std :: map

Seb*_*ann 22 c++ inheritance stdmap virtual-destructor

我想继承std::map,但据我所知std::map,没有任何虚拟析构函数.

因此可以std::map在我的析构函数中显式调用析构函数以确保正确的对象破坏吗?

Luc*_*ore 33

析构函数确实被调用,即使它不是虚拟的,但这不是问题.

如果您尝试通过指向a的指针删除类型的对象,则会得到未定义的行为std::map.

使用组合而不是继承,std容器不应该被继承,你不应该.

我假设您想要扩展功能std::map(假设您想要找到最小值),在这种情况下,您有两个更好,更合法的选项:

1)根据建议,您可以使用组合:

template<class K, class V>
class MyMap
{
    std::map<K,V> m;
    //wrapper methods
    V getMin();
};
Run Code Online (Sandbox Code Playgroud)

2)免费功能:

namespace MapFunctionality
{
    template<class K, class V>
    V getMin(const std::map<K,V> m);
}
Run Code Online (Sandbox Code Playgroud)

  • +1始终支持组合而不是继承.仍然希望有一些方法可以减少包装所需的所有样板代码. (11认同)
  • C++对`is-implemented-by`有'private`继承,``is-a`有`public`继承. (5认同)
  • @daramarak:*仍然希望有一些方法可以减少包装所需的所有样板代码*:是的,有:继承.但是程序员自信他们不应该使用它......因为他们总是倾向于把它解释为"是一个".但这不是一个要求,只是公开的信念. (3认同)

Emi*_*lia 17

存在一种误解:继承 - 除了纯粹的OOP的概念,C++不是 - 只不过是"具有未命名成员的组合,具有衰变能力".

缺少虚函数(并且析构函数在这个意义上并不特殊)会使您的对象不是多态的,但如果您正在做的只是"重用它行为并公开本机接口"继承完全按照您的要求进行.

析构函数不需要彼此显式调用,因为它们的调用始终由规范链接.

#include <iostream>
unsing namespace std;

class A
{
public:
   A() { cout << "A::A()" << endl; }
   ~A() { cout << "A::~A()" << endl; }
   void hello() { cout << "A::hello()" << endl; }
};

class B: public A
{
public:
   B() { cout << "B::B()" << endl; }
   ~B() { cout << "B::~B()" << endl; }
   void hello() { cout << "B::hello()" << endl; }
};

int main()
{
   B b;
   b.hello();
   return 0;
}
Run Code Online (Sandbox Code Playgroud)

将输出

A::A()
B::B()
B::hello()
B::~B()
A::~A()
Run Code Online (Sandbox Code Playgroud)

用A嵌入到B中

class B
{
public:
   A a;
   B() { cout << "B::B()" << endl; }
   ~B() { cout << "B::~B()" << endl; }
   void hello() { cout << "B::hello()" << endl; }
};
Run Code Online (Sandbox Code Playgroud)

这将输出完全相同.

"如果析构函数不是虚拟的,则不导出"不是C++强制性结果,而只是一个普遍接受的未编写的(在规范中没有任何内容:除了基础上的UB调用删除)之前出现的规则++ 99,当动态继承和虚函数的OOP是C++唯一支持的编程范式时.

当然,世界各地的许多程序员都用这种学校制作骨头(同样教导iostream作为原始语言,然后移动到数组和指针,在最后一课,老师说"哦... tehre也是具有向量,字符串和其他高级功能的STL")今天,即使C++成为多范式,仍然坚持使用这种纯OOP规则.

在我的示例中,A ::〜A()与A :: hello完全不是虚拟的.这是什么意思?

简单:出于同样的原因,调用A::hello不会导致调用B::hello,调用A::~A()(通过删除)将不会导致B::~B().如果你能接受 - 编程风格 - 第一个断言,你没有理由不接受第二个.在我的样本中没有A* p = new B会收到,delete p因为A ::〜A不是虚拟的,我知道这意味着什么.

正是同样的原因,不会使,使用第二个例子中的B,A* p = &((new B)->a);delete p;,虽然这第二种情况,与第一个完美的双,看起来没有明显的理由不感兴趣的人.

唯一的问题是"维护",在某种意义上 - 如果由OOP程序员查看yopur代码 - 将拒绝它,不是因为它本身是错误的,而是因为他被告知要这样做.

实际上,"如果析构函数不是虚拟的,则不导出"是因为大多数程序员都认为有太多的程序员不知道他们不能在指向基类的指针上调用delete.(对不起,如果这不礼貌,但经过30多年的编程经验,我看不到任何其他原因!)

但你的问题是不同的:

调用B :: ~B()(通过删除或通过范围结束)将始终产生A ::〜A(),因为A(无论是嵌入还是继承)在任何情况下都是B的一部分.


遵循Luchian评论:在他的评​​论中上面提到的未定义行为与删除指向对象的指针 - 没有虚拟析构函数.

根据OOP学校,这导致规则"如果不存在虚拟析构函数则不导出".

我在这里指出的是,该学校的原因取决于每个面向OOP的对象必须是多态的,并且一切都是多态的,必须通过指向基址的指针来寻址,以允许对象替换.通过做出这些断言,该学校故意试图使派生和不可替换之间的交叉无效,这样纯粹的OOP程序就不会经历那个UB.

简单地说,我的立场承认C++不仅仅是OOP,并且默认情况下并非所有C++对象都必须面向OOP,并且承认OOP并不总是必需的,并且承认C++继承并不总是必须为OOP服务代换.

std :: map不是多态的,因此它不可替换.MyMap是相同的:NOT polymorphic并且不可替换.

它只需重用std :: map并公开相同的std :: map接口.继承只是避免重写函数的冗长模板的方法,这些函数只调用重用函数.

MyMap不会有虚拟dtor,因为std :: map没有.对我而言,这足以告诉C++程序员这些不是多态对象,不能在另一个对象的位置使用.

我不得不承认,大多数C++专家今天都没有这个立场.但我认为(我唯一的个人观点)这只是因为他们的历史,与OOP作为服务的教条有关,而不是因为C++的需要.对我来说,C++不是纯粹的OOP语言,并且在不遵循或不需要OOP的上下文中,不一定总是遵循OOP范例.

  • @LuchianGrigore:*你是说可以继承只是为了重用代码,而不是使用包装方法?*我只是说**"为什么不,如果你不做OOP?"**.*OOP远不止于此.*可能会让你感到惊讶,但是......我知道.完美.但我也知道OOP不是一切.*如果你没有指向基类的指针,你就不会抽象得足够.*:我和你之间的区别在于我认为应该由上下文定义"足够".你的立场是合法的,但这还不足以使我的"错". (6认同)
  • @LuchianGrigore:*标准明确指出在我提到的情况下会出现未定义的行为.*.是的,但这不是我提到的情况,而不是OP所处的情况.*意思是,在一个好的设计中,如果你使用继承,你最终会得到实际指向MyMap*的std :: map*:通常为FALSE,仅对基于纯指针的OOP为真.这完全是我的样本不是.你如何解释我的样本的存在,不使用多态和指针? (4认同)
  • 你在那里做了一些危险的陈述.不要将虚拟析构函数的需要视为过时.标准**清楚地表明**在我提到的情况下出现了未定义的行为.抽象是OOP的重要组成部分.这意味着您不仅要导出重用,还要隐藏实际类型.意思是,在一个好的设计中,如果你使用继承,你最终会得到实际指向`MyMap`的`std :: map*`.如果你删除它,任何事情都可能发生,包括崩溃. (3认同)
  • @LuchianGrigore:无论如何,我认为你是*正确的*:我所宣称的是危险的,但不是因为程序的正确性,而是基于OOP编程的文化!但不要担心:你的反应是预期的! (2认同)

Mat*_* M. 12

我想继承std::map[...]

为什么?

继承有两个传统原因:

  • 重用它的接口(因此,对它编码的方法)
  • 重用它的行为

前者没有任何意义,因为map没有任何virtual方法,所以你不能通过继承来修改它的行为; 后者是对继承使用的歪曲,这最终只会使维护变得复杂.


如果没有明确的预期用法(在你的问题中没有上下文),我会假设你真正想要的是提供一个类似地图的容器,并有一些额外的操作.有两种方法可以实现这一目标:

  • 组合:您创建一个新对象,其中包含一个std::map,并提供足够的接口
  • 扩展:您创建可在其上运行的新自由函数 std::map

后者更简单,但它也更开放:原始界面std::map仍然是敞开的; 因此它不适合限制操作.

毫无疑问,前者更重量级,但提供了更多的可能性.

由您决定哪两种方法更合适.