09 构造函数能调用虚函数吗?
【本文链接】
http://www.cnblogs.com/hellogiser/p/whether-constructor-can-call-virtual-function.html
【题目】
构造函数可以调用虚函数吗?语法上通过吗?语义上可以通过吗?
【分析】
构造函数调用虚函数(virtual function),语法上可以通过(程序可以正常执行),但是语义上通不过(执行结果不是我们想要的)
请看以下代码
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
/*
version: 1.0 author: hellogiser blog: http://www.cnblogs.com/hellogiser date: 2014/9/29 */ #include "stdafx.h" #include <iostream> using namespace std; class Base { public: Base() { Foo(); } virtual void Foo() { cout << "Base::Foo " << 1 << std::endl; } }; class Derived : public Base { public: Derived() : Base(), m_pData(new int(2)) {} ~Derived() { delete m_pData; } virtual void Foo() { cout << "Derived::Foo " << *m_pData << std::endl; } private: int *m_pData; }; void test() { Base *p = new Derived(); delete p; } int main() { test(); return 0; } /* Base::Foo 1 */ |
执行结果是Base::Foo 1
这表明第19行执行的的是Base::Foo()而不是Derived::Foo(),也就是说:虚函数在构造函数中“不起作用”。为什么?
当实例化一个派生类对象时,首先进行基类部分的构造,然后再进行派生类部分的构造。即创建Derived对象时,会先调用Base的构造函数,再调用Derived的构造函数。当在构造基类部分时,派生类还没被完全创建,从某种意义上讲此时它只是个基类对象。即当Base::Base()执行时Derive对象还没被完全创建,此时它被当成一个Base对象,而不是Derive对象,因此Foo绑定的是Base的Foo。
C++之所以这样设计是为了减少错误和Bug的出现。假设在构造函数中虚函数仍然“生效”,即Base::Base()中的Foo();所调用的是Derive::Foo()。当Base::Base()被调用时派生类中的数据m_pData还未被正确初始化,这时执行Derive::Foo()将导致程序对一个未初始化的地址解引用,得到的结果是不可预料的,甚至是程序崩溃(访问非法内存)。
总结来说:基类部分在派生类部分之前被构造,当基类构造函数执行时派生类中的数据成员还没被初始化。如果基类构造函数中的虚函数调用被解析成调用派生类的虚函数,而派生类的虚函数中又访问到未初始化的派生类数据,将导致程序出现一些未定义行为和bug。
构造函数直接调用纯虚函数(pure virtual function),编译会报错: unresolved externals
【代码】
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
/*
version: 1.0 author: hellogiser blog: http://www.cnblogs.com/hellogiser date: 2014/9/29 */ #include "stdafx.h" #include <iostream> using namespace std; class Base { public: Base() { Foo(); } virtual void Foo() = 0; // pure virtual function }; class Derived : public Base { public: Derived() : Base(), m_pData(new int(2)) { } ~Derived() { delete m_pData; } virtual void Foo() { cout << "Derived::Foo " << *m_pData << std::endl; } private: int *m_pData; }; void test() { Base *p = new Derived(); delete p; } int main() { test(); return 0; } |
如果编译器都能够在编译或链接时识别出这种错误调用,那么我们犯错的机会将大大减少。只是有一些比较不直观的情况,编译器是无法判断出来的。这种情况下它可以生成可执行文件,但是当程序运行时会出错,因为此时Base::Foo只声明没定义。
构造函数间接调用纯虚函数(pure virtual function)(即:构造函数调用普通函数,但是普通函数又调用了纯虚函数),编译阶段不会报错,可以生成可执行文件,但是运行会出错,因为纯虚函数没有定义。
【代码】
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
/*
version: 1.0 author: hellogiser blog: http://www.cnblogs.com/hellogiser date: 2014/9/29 */ #include "stdafx.h" #include <iostream> using namespace std; class Base { public: Base() { Function(); } void Function() { Foo(); // normal function calls pure virtual function } virtual void Foo() = 0; // pure virtual function }; class Derived : public Base { public: Derived() : Base(), m_pData(new int(2)) { } ~Derived() { delete m_pData; } virtual void Foo() { cout << "Derived::Foo " << *m_pData << std::endl; } private: int *m_pData; }; void test() { Base *p = new Derived(); delete p; } int main() { test(); return 0; } |
结论:永远不要在类的构造或者析构过程中调用虚函数,因为这样的调用永远不会沿类继承树往下传递到子类中去。
【参考】
http://www.cnblogs.com/carter2000/archive/2012/04/28/2474960.html