一. 虚析构函数
我们知道,为了能够正确的调用对象的析构函数,一般要求具有层次结构的顶级类定义其析构函数为虚函数。因为在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这种纯虚函数语法就是没有定义体的语义。
其实,这是不对的。这种语法只是表明这个函数是一个纯虚函数,因此这个类变成了抽象类,不能产生对象。我们完全可以为纯虚函数指定函数体 (http://www.research.att.com/~bs/bs_faq2.html#pure-virtual)。通常的纯虚函数不需要函数体,是因为我们一般不会调用抽象类的这个函数,只会调用派生类的对应函数。这样,我们就有了一个纯虚析构函数的函数体,上面的代码需要改成:
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定义为纯虚的。(当然,完全可以定 义~Base函数,这就和上面的讨论一样了。^_^)
另外,可以看到,在main中有两种调用方法,第一种是普通的方式,进行动态链接,执行虚函数,得到结果"Derived::Hiberarchy";第二种是指定类的方式,就不再执行虚函数的动态链接过程了,结果是"Base::Hiberarchy"。
通过上面的分析可以看出,定义纯虚函数的真正目的是为了定义抽象类, 而并不是函数本身。与之对比,在java中,定义抽象类的语法是 abstract class,也就是在类的一级作指定(当然虚函数还是也要加上abstract关键字)。是不是这种方式更好一些呢?在Stroustrup的《C++语 言的设计与演化》中我找到这样一段话:
“我选择的是将个别的函数描述为纯虚的方式,没有采用将完整的类声明定义为抽象的形式,这是因为纯虚函数的概念更加灵活一些。我很看重能够分阶段定义类的能力;也就是说,我发现预先定义一些纯虚函数,并把另外一些留给进一步的派生类去定义也是很有用的”。
我 还没有完全理解后一句话,我想从另外一个角度来阐述这个概念。那就是,在一个多层复杂类结构中,中间层次的类应该具体化一些抽象函数,但很可能并不是所有 的。中间类没必要知道是否具体化了所有的虚函数,以及其祖先已经具体化了哪些函数,只要关注自己的职责就可以了。也就是说,中间类没必要知道自己是否是一 个真正的抽象类,设计者也就不用考虑是否需要在这个中间类的类级别上加上类似abstract的说明了。
当然,一个语言的设计有多种因素,好坏都是各个方面的。这只是一个解释而已。
最后,总结一下关于虚函数的一些常见问题:
1) 虚函数是动态绑定的,也就是说,使用虚函数的指针和引用能够正确找到实际类的对应函数,而不是执行定义类的函数。这是虚函数的基本功能,就不再解释了。
2) 构造函数不能是虚函数。而且,在构造函数中调用虚函数,实际执行的是父类的对应函数,因为自己还没有构造好, 多态是被disable的。
3) 析构函数可以是虚函数,而且,在一个复杂类结构中,这往往是必须的。
4) 将一个函数定义为纯虚函数,实际上是将这个类定义为抽象类,不能实例化对象。
5) 纯虚函数通常没有定义体,但也完全可以拥有。
6) 析构函数可以是纯虚的,但纯虚析构函数必须有定义体,因为析构函数的调用是在子类中隐含的。
7) 非纯的虚函数必须有定义体,不然是一个错误。
8) 派生类的override虚函数定义必须和父类完全一致。除了一个特例,如果父类中返回值是一个指针或引用,子类override时可以返回这个指针(或引用)的派生。例如,在上面的例子中,在Base中定义了 virtual Base* clone(); 在Derived中可以定义为 virtual Derived* clone()。可以看到,这种放松对于Clone模式是非常有用的。
分享到:
相关推荐
虚析构函数示例 c++析构函数是否是虚函数时的差别,敬请留意
虚析构函数 析构函数的工作方式是:底层的派生类(most derived class)的析构函数先被调用,然后调用每一个基类的析构函数。 因为在C++中,当一个派生类对象通过使用一个基类指针删除,而这个基类有一个非...
我们知道,用C++开发的时候,用来做基类的类的析构函数一般都是虚函数。可是,为什么要这样做呢?下面用一个小例子来说明:
该资源的内容主要是 虚基类 虚函数成员 虚析构函数的具体的区别
析构函数也是以类名作为函数名,与构造函数不同的是在函数名前添加一个“~”符号,标识该函数是析构函数。析构函数没有返回值,甚至void类型也不可以,析构函数也没有参数,因此析构函数是不能够重载的。这是析构...
C++中基类的析构函数为什么要用virtual虚析构函数.pdf
详细解释了为什么析构函数可以是虚函数,而构造函数不能是虚函数
C_虚构造函数和虚析构函数.pdf
构造函数不能声明为虚函数,析构函数可以声明为虚函数。
析构函数 finalizer destructor 整理的关于析构函数的描述以及介绍
本文给大家分享了避免析构函数调用虚函数。
简单示例如何使用析构函数 包括析构函数的位置 作用方法等
析构函数用于析构类的实例。 备注 不能在结构中定义析构函数。只能对类使用析构函数。 一个类只能有一个析构函数。 无法继承或重载析构函数。 无法调用析构函数。它们是被自动调用的。 析构函数既没有修饰符,也没有...
C++析构函数使用virtual的原因
深析C++析构函数,C++中析构函数是相当重要一个知识点,也是个难点。文档对析构函数了部分分析,可能不怎么全,但一点深入,相互学习呵呵
实验_构造函数与析构函数.doc实验_构造函数与析构函数.doc实验_构造函数与析构函数.doc
c++ virtual 虚析构函数及虚函数的详细例子.rar
//析构函数做成员函数 }; Base::~Base()//成员函数实现 { cout; } class Derived:public Base { public: Derived(); ~Derived(); private: int *p; }; Derived::Derived() { p=new int(0);//从堆上分配一个int型...
C++中析构函数定义成虚函数的原因 本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/SearchLife/archive/2009/03/12/3985341.aspx
c#构造函数与析构函数.doc