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

  • 相关阅读:
    AWS 免费套餐
    UWP DEP0700: 应用程序注册失败。[0x80073CF9] 另一个用户已安装此应用的未打包版本。当前用户无法将该版本替换为打包版本。
    UWP 应用程序名称本地化以及商店显示名称本地化
    ES6知识整理(6)--Symbol函数
    【web前端】移动端控制台插件,手机端页面查看相关页面控制台信息
    ES6知识整理(5)--对象的扩展
    ES6知识整理(4)--数组的扩展
    【移动端web】软键盘兼容问题
    ES6知识整理(3)--函数的扩展
    ES6知识整理(2)--变量的解构赋值
  • 原文地址:https://www.cnblogs.com/hellogiser/p/whether-constructor-can-call-virtual-function.html
Copyright © 2011-2022 走看看