当前位置:  开发笔记 > 编程语言 > 正文

什么时候不应该使用虚拟析构函数?

如何解决《什么时候不应该使用虚拟析构函数?》经验,为你挑选了6个好方法。

是否有充分的理由为类声明虚拟析构函数?什么时候应该特别避免写一个?



1> sep..:

如果满足以下任何条件,则无需使用虚拟析构函数:

无意从中派生类

堆上没有实例化

无意存储在超类的指针中

没有特别的理由要避免它,除非你真的如此紧迫的记忆.


这不是一个好的答案."没有必要"与"不应该"不同,"无意"与"不可能"不同.
通过说你"无意",你就如何使用你的课程做了很大的假设.在我看来,在大多数情况下最简单的解决方案(因此默认情况下应该是)具有虚拟析构函数,并且只有在您有特定原因的情况下才能避免使用虚拟析构函数.所以我仍然很好奇这是一个很好的理由.
这并没有真正回答这个问题.你没有使用虚拟dtor的理由在哪里?
我认为,当不需要做某事时,这是一个不这样做的好理由.它遵循XP的简单设计原则.
另外添加:无意通过基类指针删除实例.

2> Richard Cord..:

要明确地回答这个问题,你即当应该没有声明虚析构函数.

C++ '98/'03

添加虚拟析构函数可能会将您的类从POD(普通旧数据)*或聚合更改为非POD.如果您的类类型在某处初始化聚合,这可以阻止您的项目编译.

struct A {
  // virtual ~A ();
  int i;
  int j;
};
void foo () { 
  A a = { 0, 1 };  // Will fail if virtual dtor declared
}

在极端情况下,这样的更改也可能导致未定义的行为,其中类以需要POD的方式使用,例如通过省略号参数传递它,或者将其与memcpy一起使用.

void bar (...);
void foo (A & a) { 
  bar (a);  // Undefined behavior if virtual dtor declared
}

[*POD类型是一种对其内存布局有特定保证的类型.该标准实际上只是说如果您要从具有POD类型的对象复制到字符数组(或无符号字符)并再次返回,则结果将与原始对象相同.

现代C++

在最近的C++版本中,POD的概念在类布局及其构造,复制和销毁之间分开.

对于省略号情况,它不再是未定义的行为,它现在通过实现定义的语义有条件地支持(N3937 - ~C++ '14 - 5.2.2/7):

...传递具有非平凡复制构造函数,非平凡移动构造函数或平凡析构函数的类类型(第9条)的潜在评估参数,没有相应的参数,通过实现有条件地支持定义的语义.

声明析构函数不是=default意味着它不是微不足道的(12.4/5)

...如果不是用户提供的,析构函数是微不足道的......

对Modern C++的其他更改减少了聚合初始化问题的影响,因为可以添加构造函数:

struct A {
  A(int i, int j);
  virtual ~A ();
  int i;

  int j;
};
void foo () { 
  A a = { 0, 1 };  // OK
}



3> Andy..:

当且仅当我有虚拟方法时,我声明一个虚拟析构函数.一旦我有虚拟方法,我不相信自己避免在堆上实例化它或存储指向基类的指针.这两个都是非常常见的操作,如果未将析构函数声明为虚拟,则通常会以静默方式泄漏资源.


如果从类中派生出来,不管你是否有其他虚函数,你都不会冒内存泄漏的风险吗?
而且,事实上,gcc上有一个警告选项,它正好警告这种情况(虚拟方法但没有虚拟dtor).

4> Jay Conrod..:

只要有delete可能在指向具有类类型的子类的对象的指针上调用,就需要虚拟析构函数.这样可以确保在运行时调用正确的析构函数,而编译器不必在编译时知道堆上对象的类.例如,假设B是以下的子类A:

A *x = new B;
delete x;     // ~B() called, even though x has type A*

如果您的代码不是性能关键,那么为您编写的每个基类添加一个虚拟析构函数是合理的,只是为了安全起见.

但是,如果您发现自己delete在紧密循环中遇到了很多对象,那么调用虚函数(即使是空函数)的性能开销可能会很明显.编译器通常不能内联这些调用,处理器可能很难预测到哪里去.这不太可能对性能产生重大影响,但值得一提.



5> mxcl..:

虚函数意味着每个分配的对象都会通过虚函数表指针增加内存开销.

因此,如果您的程序涉及分配大量的某些对象,则值得避免所有虚函数以便为每个对象保存额外的32位.

在所有其他情况下,您将节省自己的调试痛苦,使dtor虚拟化.



6> Steve Jessop..:

并非所有C++类都适合用作具有动态多态性的基类.

如果您希望您的类适合动态多态,那么它的析构函数必须是虚拟的.此外,子类可能想要覆盖的任何方法(可能意味着所有公共方法,以及可能在内部使用的一些受保护方法)必须是虚拟的.

如果你的类不适合动态多态,那么析构函数不应该被标记为虚拟,因为这样做会产生误导.它只是鼓励人们错误地使用你的课程.

这是一个不适合动态多态的类的例子,即使它的析构函数是虚拟的:

class MutexLock {
    mutex *mtx_;
public:
    explicit MutexLock(mutex *mtx) : mtx_(mtx) { mtx_->lock(); }
    ~MutexLock() { mtx_->unlock(); }
private:
    MutexLock(const MutexLock &rhs);
    MutexLock &operator=(const MutexLock &rhs);
};

这个课程的重点是坐在RAII的堆栈上.如果你传递指向这个类的对象的指针,更不用说它的子类,那么你就是在做错了.


没错,但提问者问你什么时候应该特别避免使用虚拟析构函数.对于您描述的对话框,虚拟析构函数毫无意义,但IMO无害.我不确定我是否有信心我永远不需要使用基类指针删除对话框 - 例如我将来可能希望我的父窗口使用工厂创建其子对象.所以这不是*避免*虚拟析构函数的问题,只是你可能不会打扰它.但是,不适合派生*的类上的虚拟析构函数是有害的,因为它具有误导性.
多态使用并不意味着多态性缺失.一个类有很多用例来拥有虚方法而没有虚析构函数.在几乎任何GUI工具包中考虑一个典型的静态定义对话框.父窗口将销毁子对象,并且它知道每个子对象的确切类型,但是所有子窗口也将在任何数量的位置以多态方式使用,例如命中测试,绘图,获取文本文本的辅助功能API - 演讲引擎等
推荐阅读
小色米虫_524
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有