2014-11-04 84 views
1

我以前见过这个问题,但并不清楚,或者与我遇到过的情况相同。C++非抽象析构函数继承

我有一个抽象基类。它有一个受保护的构造函数和一个析构函数。它由几个完整类型继承,这些类型也具有公共构造函数和析构函数。我遇到的问题是,如果对象被基类型引用,那么删除对象不会调用子析构函数。

class Tree 
{ 
protected: 
     Tree(){ } 
public: 
     ~Tree(){ } 
}; 

class OakTree : public Tree 
{ 
public: 
     OakTree(){ } 
     ~OakTree(){ } 
}; 

vector<Tree*> Trees; // Store objects using the base type 
Trees.push_back(new OakTree()); // Create derived object 
delete Trees[0]; // OakTree desctructor does not get called 

我该如何获得OakTree析构函数?我试图将所有的析构函数都标记为虚拟的,但这并不起作用。基类析构函数不能是抽象的(这将解决调用问题,但不解决删除问题)。

+0

对不起,我不得不使其显示在我的记忆中转储内存泄漏让我觉得析构函数没有被调用析构函数的一个内部错误。我99%肯定在这种情况下析构函数应该是虚拟的,但是我把它们作为虚拟的,但是这并没有解决我的内存泄漏问题。不小心在我的部分,但为你的答案+1。 – 2014-11-04 16:14:42

回答

7

让您的基础级析构函数virtual

class Tree 
{ 
protected: 
     Tree(){ } 
public: 
     virtual ~Tree(){ } 
} 

否则,如果您尝试通过基类指针进行删除,则会导致未定义的行为。这是一个有点过时,但斯科特迈尔斯在有效的C++五颜六色表达了这样的,第2版:

C++语言标准是关于这一主题异常清晰:当您试图通过一个基类中删除一个派生类对象指针和基类有一个非虚拟的析构函数(如EnemyTarget那样),结果是未定义的。这意味着编译器可以生成代码以执行任何他们喜欢的操作:重新格式化磁盘,向您的老板发送暗示性邮件,将源代码传真给您的竞争对手,无论如何。 (运行时经常发生的是派生类的destrutcor从来没有被调用......)

+0

这实际上是特定于实现的,我相当肯定现有的实现不会随机调用函数,特别是格式化磁盘驱动器。我测试过的编译器称为“最接近的东西”,它是指针T的析构函数。 – dtech 2014-11-04 16:25:05

+0

这是*夸张*。然而,未定义的行为可能是一种安全风险,在某些情况下可以利用这种风险来实现各种攻击者的目标。 – 2014-11-04 16:34:12

+0

只是不要认为夸张在技术文献中有任何地方,我认为迈耶斯先生在他的许多陈述中试图过于“古怪”/有趣且不必要的多彩。我越来越讨厌这种行为,因为那些倾向于花费更多时间在“可感知的青年”面前培养幻想形象的讲座,而不是实际教授这个主题的讲课热衷于教授。 – dtech 2014-11-04 16:42:26

3

这是因为你的基类析构函数没有被声明为虚函数。

class Tree 
{ 
protected: 
     Tree(){ } 
public: 
     virtual ~Tree(){ } 
} 
0

如果你要使用多态,你必须在你的基类中有一个虚析构函数。

问题是,当你调用Tree *上的删除时,没有人真的知道它是什么类型的树,并且通过具有虚拟析构函数,编译器通过指向该函数指针的指针生成一个调用,以获取该特定对象的函数实例,并且你可以得到任何类型的析构函数。

否则该标准说它是未定义的行为,但似乎大多数情况下发生的事情是编译器生成一个对任何类型多态指针或引用的析构函数的调用,在你的情况下,这是基类。

0

我想知道为什么没有人真的问作者想用代码实现什么?

虽然,是的,有在基类虚析构函数会帮助你得到你叫析构函数,什么你仍然留下的是未定义行为

你认为该行delete Trees[0];后的结果如何?

或者,你试图用这条线实现什么?

如果你想删除Vector中的项目,你可能想:

Trees.erase(Trees.begin()); 

但后来,你留下了内存泄漏,如果你忘记删除线。

因此,请不要在向量中使用普通指针。试想一下:

std::vector< std::shared_ptr<Tree> > Trees; 
Trees.push_back(std::make_shared<OakTree>()); 
Trees.erase(Trees.begin()); 

的Runnable @Ideone

+0

这是来自大型项目的简化代码。基类存储一些数据,派生类对数据进行多种操作。派生类还有本地资源,需要在对象的生命周期结束时删除。我意识到媒介如何工作,而这不是问题的一部分。 – 2014-11-04 16:45:11

+0

内存管理呢?如果只是针对析构函数,那么在你的问题中不需要任何向量代码 – 2014-11-04 16:52:51

+0

只是为了引起人们注意,我将它存储为'Tree *'而不是'OakTree *'或'Tree'。让我们不要太挑剔。问题得到解答。 – 2014-11-04 16:55:49