解释析构函数为什么要声明成虚函数

首先要明确:

1.每个析构函数(不加 virtual) 只负责清除自己的成员。
2.可能有基类指针,指向的确是派生类成员的情况。(这是很正常的),
   那么当析构一个指向派生类成员的基类指针时,程序就不知道怎么办了。
   所以要保证运行适当的析构函数,基类中的析构函数必须为虚析构。

        基类指针可以指向派生类的对象(多态性),如果删除该指针delete []p;就会调用该指针指向的派生类析构函数,而派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象完全被释放。如果析构函数不被声明成虚函数,则编译器实施静态绑定,在删除基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会造成派生类对象析构不完全。所以,将析构函数声明为虚函数是十分必要的。

 

示例代码:

1.第一段代码

#include<iostream>
using namespace std;
class ClxBase{
public:
ClxBase() {};
~ClxBase() {cout << "Output from the destructor of class ClxBase!" << endl;};

void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
};

class ClxDerived : public ClxBase{
public:
ClxDerived() {};
~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };

void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };
};
int main(){
ClxDerived *p = new ClxDerived;
p->DoSomething();
delete p;
return 0;
}

运行结果:

Do something in class ClxDerived!

Output from the destructor of class ClxDerived!

Output from the destructor of class ClxBase!

这段代码中基类的析构函数不是虚函数,在main函数中用继承类的指针去操作继承类的成员,释放指针P的过程是:先释放继承类的资源,再释放基类资源.

2.第二段代码

#include<iostream>
using namespace std;
class ClxBase{
public:
ClxBase() {};
~ClxBase() {cout << "Output from the destructor of class ClxBase!" << endl;};

void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
};

class ClxDerived : public ClxBase{
public:
ClxDerived() {};
~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };

void DoSomething() { cout << "Do something in class ClxDerived!" << endl; }
};
int main(){
ClxBase *p = new ClxDerived;
p->DoSomething();
delete p;
return 0;
}

输出结果:

Do something in class ClxBase!
Output from the destructor of class ClxBase!

这段代码中基类的析构函数同样不是虚函数,不同的是在main函数中用基类的指针去操作继承类的成员,释放指针P的过程是:只是释放了基类的资源,而没有调用继承类的析构函数.调用dosomething()函数执行的也是基类定义的函数.

一般情况下,这样的删除只能够删除基类对象,而不能删除子类对象,形成了删除一半形象,造成内存泄漏.

在公有继承中,基类对派生类及其对象的操作,只能影响到那些从基类继承下来的成员.如果想要用基类对非继承成员进行操作,则要把基类的这个函数定义为虚函数.

析构函数自然也应该如此:如果它想析构子类中的重新定义或新的成员及对象,当然也应该声明为虚的.

3.第三段代码:

#include<iostream>
using namespace std;
class ClxBase{
public:
ClxBase() {};
virtual ~ClxBase() {cout << "Output from the destructor of class ClxBase!" << endl;};
virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
};

class ClxDerived : public ClxBase{
public:
ClxDerived() {};
~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };
void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };
};

int main(){
ClxBase *p = new ClxDerived;
p->DoSomething();
delete p;
return 0;
}

运行结果:

Do something in class ClxDerived!
Output from the destructor of class ClxDerived!
Output from the destructor of class ClxBase!

这段代码中基类的析构函数被定义为虚函数,在main函数中用基类的指针去操作继承类的成员,释放指针P的过程是:只是释放了继承类的资源,再调用基类的析构函数.调用dosomething()函数执行的也是继承类定义的函数.

如果不需要基类对派生类及对象进行操作,则不能定义虚函数,因为这样会增加内存开销.当类里面有定义虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间.所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数.

 

内容来自:

http://zhidao.baidu.com/question/207712177.html

http://blog.sina.com.cn/s/blog_7c773cc50100y9hz.html

       转过来是为了做一个笔记,方便自己以后查看这个知识点。感谢以上链接处的作者们!

原文链接

<think>嗯,用户问的是为什么析构函数声明虚函数。这个问题看起来是关于C++的,因为析构函数虚函数在C++里特别重要。首先,我得回忆一下C++中类的继承和动态多态的相关知识。 用户可能已经知道析构函数的作用,即在对象销毁时释放资源。但为什么有时候需要将基类的析构函数设为虚函数呢?可能他们在使用基类指针指向派生类对象时遇到了问题,比如内存泄漏或者未正确调用派生类的析构函数。 首先,我得考虑当用基类指针删除派生类对象时会发生什么。如果基类的析构函数不是虚函数,那么编译器会根据指针的类型来调用析构函数,而不是对象的实际类型。这会导致派生类的析构函数没有被调用,从而可能引发资源泄露,比如内存没有释放,文件句柄未关闭等。 需要举一个例子来说明问题。比如基类Base有非虚析构函数,派生类Derived有自己的析构函数。当使用Base* ptr = new Derived(); delete ptr;时,只有Base的析构函数被调用,Derived的析构函数没有被执行,导致资源泄漏。这时候如果Base的析构函数虚函数,就能正确调用Derived的析构函数,再调用Base的析构函数,避免泄漏。 另外,用户可能想知道虚析构函数的工作原理。在C++中,如果一个类有虚函数,它通常会有虚函数表(vtable),其中包含指向虚函数的指针。当析构函数声明虚函数时,派生类的析构函数会覆盖基类的析构函数,确保通过基类指针删除对象时,正确的析构函数链被调用。 可能用户也想知道什么时候需要将析构函数设为虚函数。一般来说,如果一个类可能被继承,并且可能通过基类指针来删除对象,那么基类的析构函数必须是虚的。反之,如果一个类不是设计为基类,或者不会被多态使用,析构函数不必是虚函数,避免不必要的开销。 此外,用户可能对虚析构函数的性能影响有疑问。虚函数会引入额外的开销,比如虚表指针和间接调用,但在大多数情况下,这种开销可以忽略不计,特别是考虑到正确释放资源的重要性。 还需要提到C++11引入的override和final关键字,这些可以帮助防止错误。比如,派生类中的析构函数可以标记为override,确保基类析构函数是虚的。如果基类析构函数不是虚的,编译器会报错,帮助捕获潜在的问题。 可能用户还不太清楚虚析构函数在多继承中的表现,这时候需要说明每个基类如果有虚析构函数,其派生类对象会正确调用所有相关析构函数,遵循构造和析构的顺序。 总结来说,用户需要理解虚析构函数在保证对象正确销毁中的重要性,特别是在多态使用的情况下。通过基类指针删除派生类对象时,虚析构函数确保整个对象层次结构的析构函数被正确调用,避免资源泄漏。同时,需要指导用户正确使用虚析构函数的情况,以及相关的最佳实践,比如将可能被继承的类的析构函数声明虚函数,以增强代码的安全性和健壮性。</think>--- ### **一、问题的核心:为什么析构函数需要声明虚函数?** 在C++中,**虚析构函数**的核心作用是确保**通过基类指针删除派生类对象时,能够正确调用派生类的析构函数**。 如果基类析构函数**不是虚函数**,则可能仅调用基类的析构函数,导致派生类的资源未释放,引发内存泄漏或逻辑错误。 --- ### **二、关键机制:动态绑定与多态性** C++通过**虚函数表(vtable)**实现动态多态。当基类析构函数声明虚函数时: 1. 派生类析构函数会**隐式覆盖基类析构函数**。 2. 通过基类指针删除对象时,实际调用的是**对象实际类型(派生类)的析构函数**,触发完整的析构链。 --- ### **三、具体场景分析** #### **场景1:非虚析构函数导致资源泄漏** ```cpp class Base { public: ~Base() { std::cout << "Base析构\n"; } // 非虚析构函数 }; class Derived : public Base { public: ~Derived() { std::cout << "Derived析构\n"; } // 不会自动调用 }; int main() { Base* ptr = new Derived(); delete ptr; // 仅输出 "Base析构",Derived析构未执行! return 0; } ``` **结果**:`Derived`的析构函数未被调用,其资源(如堆内存、文件句柄)未释放。 #### **场景2:虚析构函数修复问题** ```cpp class Base { public: virtual ~Base() { std::cout << "Base析构\n"; } // 虚析构函数 }; class Derived : public Base { public: ~Derived() override { std::cout << "Derived析构\n"; } // 正确覆盖 }; int main() { Base* ptr = new Derived(); delete ptr; // 输出 "Derived析构" → "Base析构" return 0; } ``` **结果**:通过基类指针删除对象时,**派生类析构函数优先执行**,资源正确释放。 --- ### **四、核心规则与实践建议** #### **1. 何时需要虚析构函数?** - 当类**可能被继承**,且可能**通过基类指针删除派生类对象**时,基类析构函数必须声明虚函数。 - **反例**:若类不会被继承(如工具类、单例),或仅通过具体类型使用,无需虚析构函数。 #### **2. 虚析构函数的工作原理** - **析构顺序**:派生类析构 → 基类析构(与构造顺序相反)。 - **虚表机制**:虚析构函数通过虚函数表动态绑定,确保调用正确的析构函数。 #### **3. C++11后的最佳实践** - 使用`override`明确派生类析构函数的意图: ```cpp class Derived : public Base { public: ~Derived() override { ... } // 显式声明覆盖基类虚析构 }; ``` - 若类设计为不可继承,可用`final`关键字禁止派生: ```cpp class NonInheritable final { ... }; ``` --- ### **五、性能与设计权衡** #### **1. 虚析构函数的开销** - **虚表指针**:每个对象增加一个指针大小(通常4~8字节)。 - **动态绑定**:析构时多一次间接调用(性能影响可忽略)。 - **权衡**:在需要多态的场景下,正确性远大于微小性能损失。 #### **2. 错误设计示例** ```cpp class ResourceHolder { public: ResourceHolder() { data_ = new int[100]; } ~ResourceHolder() { delete[] data_; } // 非虚析构 private: int* data_; }; class ExtendedHolder : public ResourceHolder { public: ExtendedHolder() { file_ = fopen("data.txt", "r"); } ~ExtendedHolder() { fclose(file_); } // 不会被调用! private: FILE* file_; }; int main() { ResourceHolder* obj = new ExtendedHolder(); delete obj; // 仅释放data_,file_未关闭! } ``` **问题**:基类`ResourceHolder`未声明析构函数,导致`ExtendedHolder`的资源泄漏。 --- ### **六、总结** **虚析构函数的意义**: 1. **保证资源安全**:通过基类指针删除对象时,触发完整的析构链。 2. **多态的必要条件**:虚析构函数是实现多态销毁的关键。 3. **代码健壮性**:避免因继承关系导致的隐蔽资源泄漏。 **实践原则**: - 若类可能作为基类,析构函数必须为虚函数。 - 若类设计为不可继承,使用`final`或非虚析构函数。 - 结合`override`和`final`提高代码可读性和安全性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值