zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    215. Kth Largest Element in an Array
    214. Shortest Palindrome
    213. House Robber II
    212. Word Search II
    210 Course ScheduleII
    209. Minimum Size Subarray Sum
    208. Implement Trie (Prefix Tree)
    207. Course Schedule
    206. Reverse Linked List
    sql 开发经验
  • 原文地址:https://www.cnblogs.com/hellogiser/p/whether-constructor-can-call-virtual-function.html
Copyright © 2011-2022 走看看