C++中析构函数能够为纯虚函数吗?
众所周知。在实现多态的过程中,一般将基类的析构函数设为virtual
。以便在delete
的时候能够多态的链式调用。那么析构函数能否够设为纯虚呢?
class CBase{ public: CBase() { printf("CBase()\n"); } virtual ~CBase() = 0; // 析构函数是纯虚函数};
答案是能够。那么这样实现的目的是什么呢?当然是避免实例化。
但由于派生类不可能来实现基类的析构函数,所以基类析构函数尽管能够标为纯虚,可是仍必须实现析构函数,否则派生类无法继承。也无法编译通过。
以下详细讨论:
虚析构函数
我们知道,为了能够正确的调用对象的析构函数,一般要求具有层次结构的顶级类定义其析构函数为虚函数。由于在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
的错误报告)。
error LNK2019: unresolved external symbol "public: virtual __thiscall Base::~Base(void)" (??1Base@@UAE@XZ) referenced in function "public: virtual __thiscall Derived::~Derived(void)" (??
1Derived@@UAE@XZ) 1>D:\C++\exercise\Debug\exercise.exe : fatal error LNK1120: 1 unresolved externals
这是由于。析构函数、构造函数和其它内部函数不一样。在调用时,编译器须要产生一个调用链。
也就是,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 { Base::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
的说明了。
当然,一个语言的设计有多种因素。好坏都是各个方面的。
这仅仅是一个解释而已。
最后,总结一下关于虚函数的一些常见问题:
虚函数是动态绑定的,也就是说。使用虚函数的指针和引用能够正确找到实际类的相应函数,而不是运行定义类的函数。
这是虚函数的基本功能,就不再解释了。
构造函数不能是虚函数。并且,在构造函数中调用虚函数,实际运行的是父类的相应函数。由于自己还没有构造好, 多态是被
disable
的。析构函数能够是虚函数。并且,在一个复杂类结构中。这往往是必须的。
将一个函数定义为纯虚函数。实际上是将这个类定义为抽象类,不能实例化对象。
纯虚函数通常未定义体,但也全然能够拥有, 甚至能够显示调用。
析构函数能够是纯虚的,但纯虚析构函数必须有定义体,由于析构函数的调用是在子类中隐含的。
非纯的虚函数必须有定义体,不然是一个错误。
派生类的
override
虚函数定义必须和父类全然一致(c++11
中使用override
进行编译器检查)。除了一个特例,假设父类中返回值是一个指针或引用。子类override
时能够返回这个指针(或引用)的派生。比如,在上面的样例中,在
Base
中定义了virtual Base* clone()
; 在Derived
中能够定义为virtual Derived* clone()
。能够看到,这样的放松对于
Clone
模式是非常实用的(也就是说override
并不会检查返回值类型)。