09 构造函数能调用虚函数吗?

【本文链接】

http://www.cnblogs.com/hellogiser/p/whether-constructor-can-call-virtual-function.html

【题目】

构造函数可以调用虚函数吗?语法上通过吗?语义上可以通过吗?

【分析】

构造函数调用虚函数(virtual function),语法上可以通过(程序可以正常执行),但是语义上通不过(执行结果不是我们想要的)

请看以下代码

 C++ Code 
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

【代码】

 C++ Code 
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)(即:构造函数调用普通函数,但是普通函数又调用了纯虚函数),编译阶段不会报错,可以生成可执行文件,但是运行会出错,因为纯虚函数没有定义。

【代码】

 C++ Code 
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

http://blog.csdn.net/hxz_qlh/article/details/14089895