c++编程习惯六(给基类(多态)声明virtual(虚)析构函数)

我们先来看一个例子:设计一个人-类,Human和一些派生类:

class Human{
public:
    Human();
    ~Human();
    ...
};

class Teacher:public Human{...};//老师
class Student:public Human{...};//学生
class Boss:public Human{...};//老板

有时候比如在一个系统中会出现很多不同的职业,于是我们就把人这个类抽象成一个基类:

Human* gethuman();//返回一个指针,指向Human派生类的动态分配对象。

被返回的对象位于堆内存,因此在程序结束时我们要delete。

Human* pstu=gethuman();
...
delete pstu;//释放

我们想一下,我们这么做,似乎在流程来说好像都是正确的,但即便如此,我们仍然没办法知道程序如何行动。

问题出在哪呢?

当我们使用gethuman返回的指针指向一个派生类对象时,例如(Student),但是那个对象确是由基类指针删除,但是目前的基类指针有一个非虚析构函数。

我们先来捋一捋,在c++中我们是允许使用一个基类指针去指向派生类的对象的,这也是继承的手段。但是我们这里在delete时发生了什么,我们delete的是一个基类指针,那么基类又正好有一个non-virtual析构函数,这时,c++明白指出了,当派生对象经由一个基类指针删除,而该基类带着一个non-virtual析构函数,其结果未有定义——实际执行时通常发生的是对象的派生成分没被销毁。例如gethuman返回的指针指向一个Student对象,其内的Student成分很可能没被销毁,而且Student的析构函数也未能执行。我们只会销毁基类成分。这可是形成资源泄露、败坏数据结构、浪费调试时间的途径。

 

想要消除这个问题的做法很简单:给基类一个虚析构函数。之后,删除派生类对象时就会像你想的那样,会销毁整个对象,包括所有的派生类成分:

class Human{
public:
    Human();
    virtual ~Human();
    ...
};

Human*pstu=gethuman();
...
delete pstu;//行为正确

像Human这样的基类通常还会有其他的virtual函数,因为virtual函数的目的就是允许派生类去覆盖它。我们可以明确,任何class只要带有virtual函数都几乎带有virtual析构函数。

如果class不含virtual函数,通常意味着它并不意图被用作一个基类。当class不企图被当做基类时,我们也不应该让它的析构函数为virtual。为什么呢?

因为如果一个类,如果没有虚函数,那么就意味着没有虚函数表,虚函数表的实质是一个指针,,编译期就是通过对象的虚函数表找到适当的函数指针,而我们知道,指针需要内存空间。如果本不需要这个指针的类,你给加上了虚函数,那么就浪费了内存,而且也降低了移植性,因为其他语言可能没有这样的东西,比如c语言。

因此,无故的将所有的class的析构函数声明为virtual,也是不对的。许多人的心得是:只有当class内至少含有一个virtual函数,才为它声明virtual析构函数。

 

 

即使有些类完全不带虚函数,因为c++没有其他语言那样的禁止派生机制,所以还是会有人犯这样的错误:

class Specialstring:public std::string{
...
};

这是一个馊主意,看起来似乎没什么问题,但是当我们这样做时:

Specialstring* pss=new Specialstring("hello");
std::string*ps;

ps==pss;

delete ps;//未有定义

只要不带virtual析构函数的class就不该被继承。

 

有的时候让class带一个pure virtual析构函数,可能很便利。pure virtual函数导致抽象类——也就是不能被实体化的class,也就是不能创建对象,这种类就是用来被继承的。有些时候,你希望拥有抽象类,但是没有任何的纯虚函数,那该怎么办,由于抽象类总是被当做基类来使用,又因为基类因该有一个虚析构函数,并且纯虚函数会导致类抽象化,简直完美:当你希望它成为抽象的那个类就声明一个pure virtual析构函数:

class A{
    public:
        virtual ~A()=0;//声明纯虚析构函数
}

这里还有一个小窍门,必须为这个pure virtual析构函数提供一份定义:

A::~A(){}

析构函数的运作方式是,最深层派生的那个class其析构函数最先被调用,然后是其每一个基类的西苟寒食被调用,编译器会在A的派生类的析构函数中创建一个~A的调用动作,所以你必须为这个函数提供一份定义,如果不这样做,连接器就会报错。

 

注意:

给基类一个virtual析构函数,这个规则只适用于带多态性质的基类身上。这种基类的设计目的是为了用来“通过基类接口处理派生类对象”。

并非所有的基类设计目的都是为了多态用途,例如标准string和STL容器。

总结:

1、带多态性质的基类应该声明一个virtual析构函数。如果class带有任何virtual函数,就应该拥有一个virtual析构函数。

2、class的设计目的如果不是作为基类使用,或不是为了具备多态性,就不该声明virtual析构函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我会记录下您的问题并回答。以下是代码示例: ```c++ #include <iostream> using namespace std; class BaseClass { public: // 未声明函数 // ~BaseClass() { // cout << "BaseClass destructor called." << endl; // } virtual ~BaseClass() { cout << "BaseClass virtual destructor called." << endl; } }; class DerivedClass : public BaseClass { public: ~DerivedClass() { cout << "DerivedClass destructor called." << endl; } }; int main() { BaseClass* ptr = new DerivedClass(); delete ptr; return 0; } ``` 在这个示例中,我们定义了一个类 `BaseClass` 和一个派生类 `DerivedClass`。在 `BaseClass` 中声明函数和未声明函数两种情况,并在 `main()` 中创建了一个 `DerivedClass` 对象,并将其地址赋给一个 `BaseClass` 的指针,然后通过该指针释放对象。 当 `BaseClass` 的函数声明函数时,删除 `DerivedClass` 对象时只会调用 `BaseClass` 的函数,不会调用 `DerivedClass` 的函数,导致内存泄漏。这是因为 C++ 中的多需要使用函数来实现,只有将 `BaseClass` 的函数声明函数,才能正确地调用 `DerivedClass` 的函数。 当 `BaseClass` 的函数声明函数时,删除 `DerivedClass` 对象时会先调用 `DerivedClass` 的函数,再调用 `BaseClass` 的函数,确保内存正确释放。 因此,正确的做法是在类中声明函数,并确保派生类都定义了函数

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值