What does 'has virtual method … but non-virtual destructor' warning mean during C++ compilation?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <iostream>
using namespace std;
class CPolygon {
protected:
int width, height;
public:
virtual int area ()
{ return (0); }
};
class CRectangle: public CPolygon {
public:
int area () { return (width * height); }
}; |
有编译警告
1
| Class '[C@1a9e0f7' has virtual method 'area' but non-virtual destructor |
如何理解此警告以及如何改进代码?
[编辑]这个版本现在正确吗?(试图给出答案以阐明自己的概念)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <iostream>
using namespace std;
class CPolygon {
protected:
int width, height;
public:
virtual ~CPolygon(){};
virtual int area ()
{ return (0); }
};
class CRectangle: public CPolygon {
public:
int area () { return (width * height); }
~CRectangle(){}
}; |
- 是的,新版本是正确的。尽管它被认为是将派生类中的函数重新声明为虚拟的好形式,尽管它不是必需的。这使得那些只想查看派生类的人仍然知道函数是虚拟的。
- 你是说class CRectangle: public CPolygon { public: virtual int area () { return (width * height); } };吗?
- 对。还有virtual ~CRectangle() {}。正如我所说,重申这些函数是虚拟的,这只是一种很好的形式,语言无论如何都不需要它。
- @problemania为什么在您的示例中有分号:virtual ~CPolygon(){};同时@omnifarious在上面的示例中没有分号?
- @总而言之:;是完全多余的。就其本身而言,它只是一个空语句。有时,您需要一个空语句作为while或for循环的主体,其中所有操作都是用副作用完成的。我从来没有见过一个在声明中间使用过,我确信它是意外或混乱。
- 因此,江户一号〔3〕既没有损害,也没有好处。一个混沌的中性分号?也许是因为他以前有过virtual ~CPolygon()=0;,这似乎是虚拟毁灭者的另一种常见形式(除了那个呆着的puft棉花糖人)。我不清楚=0版本是否真的相同。是那种"纯"的,对吧?
如果一个类有一个虚拟方法,那意味着您希望其他类从中继承。这些类可以通过基类引用或指针来销毁,但这仅在基类具有虚拟析构函数时才有效。如果您有一个类被认为是可以多态使用的,那么它也应该是可以多态删除的。
这个问题在这里也得到了深入的回答。下面是一个完整的示例程序,演示了这种效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include <iostream>
class FooBase {
public:
~FooBase() { std::cout <<"Destructor of FooBase" << std::endl; }
};
class Foo : public FooBase {
public:
~Foo() { std::cout <<"Destructor of Foo" << std::endl; }
};
class BarBase {
public:
virtual ~BarBase() { std::cout <<"Destructor of BarBase" << std::endl; }
};
class Bar : public BarBase {
public:
~Bar() { std::cout <<"Destructor of Bar" << std::endl; }
};
int main() {
FooBase * foo = new Foo;
delete foo; // deletes only FooBase-part of Foo-object;
BarBase * bar = new Bar;
delete bar; // deletes complete object
} |
输出:
1 2 3
| Destructor of FooBase
Destructor of Bar
Destructor of BarBase |
注意,delete bar;导致调用~Bar和~BarBase这两个析构函数,而delete foo;只调用~FooBase。后者甚至是未定义的行为,因此无法保证效果。
- 通过一个例子来证明切片的不良影响,这个答案将会得到很大的改进。当它有了它,我会给它一个上票。
- @无所不能:我添加了一个例子。
- 只是要清楚:delete foo调用未定义的行为,不能保证只运行~FooBase。
- @曼卡斯:是的,我在回答中加了一句,谢谢。
- @无所不知"切片"在这种情况下意味着什么?
- @hellogoodbye看到这个问题:stackoverflow.com/q/274626/160206
- @bj&246;rnpollex谢谢!
这意味着您需要一个带有虚方法的基类上的虚析构函数。
1 2 3 4
| struct Foo {
virtual ~Foo() {}
virtual void bar() = 0;
}; |
不使用is->strike>会导致未定义的行为,通常在valgrind等工具中显示为内存泄漏。
- 只有通过基类引用或指针删除子类对象时,它才是ub。
- 不使用它本身并不是未定义的行为。它可能导致的唯一未定义行为是,如果派生类型的动态分配对象与delete表达式一起释放,其中操作数的类型是指向基类的指针,而基类没有虚拟析构函数。还有其他一些选项可以鼓励安全使用,比如给类一个受保护的非虚拟析构函数。
- 据我所知,如果您(或某人)决定扩展该结构,它主要会导致问题。
它仅仅意味着
1 2
| CPolygon* p = new CRectangle;
delete p; |
…或者任何包裹在智能指针上的东西,基本上不会表现正确,因为CPolygon在删除时不是多态的,并且缝隙部分不会被正确破坏。
如果你不想删除crectangle和cpolygon多态性,那么这个警告是没有意义的。
- 但是,如果您不打算删除crectangle和cpolygon多态性,那么应该保护基类析构函数来在编译时强制执行它。
- @markb:不必:如果cpolygon不是抽象的(我不知道op abstraction有多深),那么crect和cpolygon都可以成为栈中真正完美的合法公民,通过引用参与cpolygon算法。该区域需要进行多态性计算,但不需要进行多态性破坏。而cpolygon则要求其本身是可销毁的(没有受保护的析构函数)。派生没有虚拟析构函数的类与派生没有所有方法都是虚拟的类没有区别。简单地说,不要期望非虚拟事物的行为是虚拟的。
- @Emiliogaravaglia:通常建议避免从具体类派生。它太开放了,无法以各种不同的形式进行无意的切片。
- @查尔斯巴利:是的,因为它通常被建议不要使用goto-s,这是建议打破一个双环。但我看不到编译器警告。没有什么不好的,但不要推荐教条或宗教。一旦了解了它们的用途,建议不要使用。
- @Emiliogaravaglia:我没有提出建议,我提出了一个共同的建议,我也给出了建议的理由。这是一个建议,我同意基于我的经验和判断,没有任何教条或宗教的基础。我只是不知道这有什么关系。
- @查尔斯巴利:对我来说,这是一个建议,我不同意基于我的经验,不一定是同你一样。我看了太多次std::string被包装,整个接口被重写,因为…"它没有虚拟的析构函数",当一个简单的派生就足够了,我认为这样的警告甚至"危险",因为它会使许多程序员的思想变形。如果fizz是虚拟的,而buzz不是虚拟的,那么您是否希望将buzz称为多态的东西?那么,为什么一个析构函数会如此不同呢?如果析构函数不是虚拟的,就不要新建/删除对象
- @Emiliogaravaglia:答案是不要从::std::string中得到东西。我不知道这里面到底有什么目的。
- @无所不能:哼…你不知道,但你有答案。这正是我所说的"宗教"。
- @Emiliogaravaglia:好吧,如果有开发人员想要从std::string派生,那么我同意您有更多的基本问题,并且我同意尝试向他们介绍将抽象基类添加到类层次结构的好处可能不是第一要务。
- @查尔斯贝利:请礼貌一点。你不能同意自己的观点。我可以把这理解为个人的侮辱。A我不想。如果这就是你的宗教所说的,那就和你的上帝一起快乐吧,让我和我的上帝一起快乐吧。
- @埃米利奥加拉瓦利亚:对不起,我真的不明白你觉得什么是侮辱。我真的以为我同意你的意见,我已经审阅了我对这篇文章的每一条评论。据我所知,我对每一个人都很有礼貌,很抱歉,尽管我努力了,你还是能够解释其中任何一个人的任何形式的侮辱或轻微的侮辱。