C++中虚析构函数和纯虚函数的作用

本文深入探讨了C++中虚析构函数和纯虚函数的概念与使用,包括它们在类层次结构中的作用,如何正确实现以及避免常见陷阱。文章强调了虚析构函数在删除派生类指针时的重要性,以及纯虚函数在创建抽象基类时的应用。

一. 虚析构函数

为了能够正确的调用对象的析构函数,一般要求具有层次结构的顶级类定义其析构函数为虚函数。因为在delete一个抽象类指针时候,必须要通过虚函数找到真正的析构函数。

class Base
{
public:
   Base(){}
   virtual ~Base(){}
};

class Derived: public Base
{
public:
   Derived(){};
   ~Derived(){};
}

void foo()
{
   Base *pb;
   pb = new Derived;
   delete pb;
} 

这是正确的用法,会发生动态绑定,它会先调用Derived的析构函数,然后是Base的析构函数。

如果析构函数不加virtual,delete pb只会执行Base的析构函数,而不是真正的Derived析构函数。
因为不是virtual函数,所以调用的函数依赖于指向静态类型,即Base。

二. 纯虚析构函数

现在的问题是,我们想把Base做出抽象类,不能直接构造对象,需要在其中定义一个纯虚函数。如果其中没有其他合适的函数,可以把析构函数定义为纯虚的,即将前面的CObject定义改成:

class Base
{
public:
   Base(){}
   virtual ~Base()= 0
};

可是,这段代码不能通过编译,通常是link错误,不能找到~Base()的引用(gcc的错误报告)。这是因为,析构函数、构造函数和其他内部函数不一样,在调用时,编译器需要产生一个调用链。也就是,Derived的析构函数里面隐含调用了Base的析构函数。而刚才的代码中,缺少~Base()的函数体,当然会出现错误。

这里面有一个误区,有人认为,virtual f()=0这种纯虚函数语法就是没有定义体的语义。
其实,这是不对的。这种语法只是表明这个函数是一个纯虚函数,因此这个类变成了抽象类,不能产生对象。我们完全可以为纯虚函数指定函数体 。通常的纯虚函数不需要函数体,是因为我们一般不会调用抽象类的这个函数,只会调用派生类的对应函数。这样,我们就有了一个纯虚析构函数的函数体,上面的代码需要改成:

class Base
{
public:
   Base()
   {
   }
   virtual ~Base() = 0; //pure virtual
};

Base::~Base()//function body
{
} 

从语法角度来说,不可以将上面的析构函数直接写入类声明中(内联函数的写法)。这或许是一个不正交化的地方。但是这样做的确显得有点累赘。

这个问题看起来有些学术化,因为一般我们完全可以在Base中找到一个更加适合的函数,通过将其定义为没有实现体的纯虚函数,而将整个类定义为抽象类。但这种技术也有一些应用,如这个例子:

class Base  //abstract class
{
public:
   virtual ~Base(){};//virtual, not pure
   virtual void Hiberarchy() const = 0;//pure virtual
};

void Base::Hiberarchy() const //pure virtual also can have function body
{
   std::cout <<"Base::Hiberarchy";
}

class Derived : public Base
{
public:
   Derived(){}
   virtual void Hiberarchy() const
   {
       CB::Hiberarchy();
       std::cout <<"Derived::Hiberarchy";
   }
   virtual void foo(){}
};


int main(){
   Base* pb=new Derived();
   pb->Hiberarchy();
   pb->Base::Hiberarchy();
   return 0;
} 

在这个例子中,我们试图打印出类的继承关系。在根基类中定义了虚函数Hiberarchy,然后在每个派生类中都重载此函数。我们再一次看到,由于想把Base做成个抽象类,而这个类中没有其他合适的方法成员可以定义为纯虚的,我们还是只好将Hiberarchy定义为纯虚的。

另外,可以看到,在main中有两种调用方法,第一种是普通的方式,进行动态链接,执行虚函数,得到结果”Derived::Hiberarchy”;第二种是指定类的方式,就不再执行虚函数的动态链接过程了,结果是”Base::Hiberarchy”。

通过上面的分析可以看出,定义纯虚函数的真正目的是为了定义抽象类,而并不是函数本身。与之对比,在java中,定义抽象类的语法是 abstract class,也就是在类的一级作指定。

虚函数小结

  1. 虚函数是动态绑定的,也就是说,使用虚函数的指针和引用能够正确找到实际类的对应函数,而不是执行定义类的函数。这是虚函数的基本功能,就不再解释了。

  2. 构造函数不能是虚函数。而且,在构造函数中调用虚函数,实际执行的是父类的对应函数,因为自己还没有构造好, 多态是被disable的。

  3. 析构函数可以是虚函数,而且,在一个复杂类结构中,这往往是必须的。

  4. 将一个函数定义为纯虚函数,实际上是将这个类定义为抽象类,不能实例化对象。

  5. 纯虚函数通常没有定义体,但也完全可以拥有。

  6. 析构函数可以是纯虚的,但纯虚析构函数必须有定义体,因为析构函数的调用是在子类中隐含的。

  7. 非纯的虚函数必须有定义体,不然是一个错误。

  8. 派生类的override虚函数定义必须和父类完全一致。除了一个特例,如果父类中返回值是一个指针或引用,子类override时可以返回这个指针(或引用)的派生。例如,在上面的例子中,在Base中定义了 virtual Base* clone(); 在Derived中可以定义为 virtual Derived* clone()。可以看到,这种放松对于Clone模式是非常有用的。

虚析构函数C++中用于确保当通过基类指针删除派生类对象时,能够正确调用派生类的析构函数。如果没有虚析构函数,只有基类的析构函数会被调用,而派生类的析构函数将被忽略,这可能导致资源泄漏或未定义行为。 ### 虚析构函数作用 1. **确保正确的析构顺序**:当使用多态方式(通过基类指针)删除派生类对象时,虚析构函数确保派生类的析构函数先被调用,然后是基类的析构函数。 2. **避免资源泄漏**:如果派生类中包含需要显式释放的资源(如动态分配的内存),虚析构函数可以确保这些资源被正确释放。 ### 示例代码 ```cpp #include <iostream> #include <string> class Base { public: Base() { std::cout << "Base constructor called." << std::endl; } virtual ~Base() { std::cout << "Base destructor called." << std::endl; } // 虚析构函数 }; class Derived : public Base { public: Derived() { std::cout << "Derived constructor called." << std::endl; } ~Derived() override { std::cout << "Derived destructor called." << std::endl; } }; int main() { Base* obj = new Derived(); delete obj; // 调用虚析构函数,确保 Derived 的析构函数被调用 return 0; } ``` ### 解释 - **虚析构函数**:`Base` 类中的 `~Base()` 被声明为虚函数,这意味着即使通过基类指针删除对象,也会调用派生类的析构函数。 - **输出结果**: ``` Base constructor called. Derived constructor called. Derived destructor called. Base destructor called. ``` 这表明在删除对象时,派生类基类的析构函数都被正确调用了。 ### 虚析构函数纯虚函数的关系 虚析构函数纯虚函数都是实现多态性的重要工具,但它们有不同的用途: - **虚析构函数**:主要用于确保通过基类指针删除派生类对象时,能够正确调用派生类的析构函数。 - **纯虚函数**:用于定义接口,要求派生类必须实现该函数。 虽然纯虚函数通常出现在抽象类中,但抽象类的析构函数可以是普通的虚函数,也可以是纯虚函数。如果是虚析构函数,它仍然需要提供一个定义(通常是空实现),以便派生类可以正确调用它。 ### 示例代码(虚析构函数) ```cpp #include <iostream> class AbstractBase { public: virtual ~AbstractBase() = 0; // 虚析构函数 }; // 必须为虚析构函数提供定义 AbstractBase::~AbstractBase() {} class ConcreteDerived : public AbstractBase { public: ~ConcreteDerived() override { std::cout << "ConcreteDerived destructor called." << std::endl; } }; int main() { AbstractBase* obj = new ConcreteDerived(); delete obj; return 0; } ``` ### 解释 - **虚析构函数**:`AbstractBase` 类的析构函数被声明为纯虚函数,但它仍然需要提供一个定义(即使是空实现),以确保派生类的析构函数能够被正确调用。 ### 结论 虚析构函数确保通过基类指针删除派生类对象时,能够正确调用派生类的析构函数,从而避免资源泄漏。虚析构函数可以用于抽象类,但它仍然需要提供一个定义。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值