【问题标题】:What does 'has virtual method ... but non-virtual destructor' warning mean during C++ compilation?在 C++ 编译期间,'有虚拟方法......但非虚拟析构函数'警告是什么意思?
【发布时间】:2012-02-04 13:36:48
【问题描述】:
#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); }
  };

有编译警告

Class '[C@1a9e0f7' has virtual method 'area' but non-virtual destructor

如何理解这个警告以及如何改进代码?

[EDIT] 这个版本现在正确吗? (试图给出答案以阐明自己的概念)

#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 在上面的例子中没有分号?
  • @CommaToast:; 完全是多余的。就其本身而言,这只是一句空话。有时您想要一个空语句作为whilefor 循环的主体,其中所有操作都带有副作用。我从未见过在声明中间使用过一个,我敢肯定它的包含是意外或混乱。

标签: c++ polymorphism virtual


【解决方案1】:

这意味着您需要在具有虚拟方法的基类上使用虚拟析构函数。

struct Foo {
  virtual ~Foo() {}
  virtual void bar() = 0;
};

不使用 is 会导致未定义的行为,通常在 valgrind 等工具中显示为内存泄漏。

【讨论】:

  • 只有通过基类引用或指针删除子类对象时才UB。
  • 关闭它本身并不是未定义的行为。它可能导致的唯一未定义行为是,如果使用delete 表达式释放派生类型的动态分配对象,其中操作数的类型是指向基类的指针,而基类没有虚拟析构函数。还有其他鼓励安全使用的选项,例如为类提供受保护的非虚拟析构函数。
  • 据我所知,如果您(或某人)决定扩展该结构,这主要会导致问题。
【解决方案2】:

如果一个类有一个虚方法,这意味着你希望其他类继承它。这些类可以通过基类引用或指针来销毁,但这仅在基类具有虚拟析构函数时才有效。如果你有一个应该多态可用的类,它也应该是多态可删除的。

这个问题也有深度回答here。下面是一个完整的示例程序,演示效果:

#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
}

输出:

Destructor of FooBase
Destructor of Bar
Destructor of BarBase

请注意,delete bar; 会导致调用 ~Bar~BarBase 这两个析构函数,而 delete foo; 仅调用 ~FooBase。后者甚至是undefined behavior,所以不能保证效果。

【讨论】:

  • 这个答案将通过一个演示切片不良影响的示例得到很大改善。当它拥有它时,它会得到我的支持。
  • @Omnifarious:我添加了一个例子。
  • 明确一点:delete foo 调用未定义的行为,不能保证只运行~FooBase
  • @Omnifarious 在这种情况下“切片”是什么意思?
【解决方案3】:

它只是意味着像这样的代码

CPolygon* p = new CRectangle;
delete p;

... 或任何包装成任何智能指针的东西, 由于 CPolygon 在删除时不是多态的,因此基本上不会正确运行,并且 CRectange 部分不会被正确销毁。

如果您不打算删除 CRectangle 和 CPolygon 多态,则该警告没有意义。

【讨论】:

  • 但是,如果您不打算删除 CRectangle 和 CPolygon 多态,则应保护基类析构函数以在编译时强制执行此操作。
  • @MarkB:不一定:如果CPolygon不是抽象的(我不知道OP抽象有多深),CRect和CPolygon都可以是真正完美的堆栈合法公民,参与CPolygon算法通过参考。面积需要多态计算,但不需要多态破坏。并且 CPolygon 本身必须是可破坏的(没有受保护的析构函数)。派生一个没有虚拟析构函数的类与派生一个没有所有虚拟方法的类没有什么不同。只是不要指望非虚拟的东西以虚拟的方式表现。
  • @EmilioGaravaglia:通常建议避免从具体类派生。对于各种不同形式的无意切片来说,它太开放了。
  • @CharlesBailey:是的,因为通常建议不要使用 goto-s,建议打破双循环。但我没有看到那些编译器警告。它本身没有什么不好,但不要推荐教条或宗教。建议不要使用,一旦了解它们的用途。
  • @EmilioGaravaglia:我不是在提出建议,我是在陈述一个共同的建议,我也给出了建议的理由。这是我根据我的经验和判断同意的建议,没有教条或宗教基础。我只是不明白这有什么关系。
猜你喜欢
  • 2012-09-21
  • 2010-09-12
  • 2012-04-18
  • 1970-01-01
  • 2014-08-09
  • 1970-01-01
  • 2015-11-12
  • 2014-02-12
  • 2011-02-04
相关资源
最近更新 更多