• 结合实例详解"pure Virtual function called"


    作者:阿波

    链接:http://blog.csdn.net/livelylittlefish/article/details/9750593

    (4年前的一篇文章,翻出来共享一下。)

    本实例即为经典的讲解C++继承、虚函数、运行时多态的实例。今天我们再用它作为讲解"pure virtual functioncalled"的实例。(在某些平台上也可能输出"pure virtual methodcalled"

     

    1. 实现基类Shapearea函数

     file:testVirtualFunc.cpp

    #include <stdio.h>
    #include <stdlib.h>
    
    #define PI 3.1415926
    
    class Shape
    {
    private:
        double ValuePerSquareUnit;
    
    protected:
        Shape(double valuePerSquareUnit):
            ValuePerSquareUnit(valuePerSquareUnit)
        {
        }
    
    public:
        virtual double area() const = 0;
    
        double value() const
        {
        	return ValuePerSquareUnit * area();
        }
    
        virtual ~Shape()
        {
            printf("Shape::~Shape() is called");
        }
    
        double getPerSquareUnit()
        {
            return ValuePerSquareUnit;
        }
    };
    
    class Rectangle : public Shape
    {
    private:
        double Width;
        double Height;
    
    public:
        Rectangle(double width, double height, double valuePerSquareUnit):
            Shape(valuePerSquareUnit),Width(width),Height(height)
        {
        }
    
        virtual ~Rectangle()
        {
        }
    
        virtual double area() const
        {
            return Width * Height;
        }
    
    };
    
    class Circle: public Shape
    {
        double Radius;
    
    public:
        Circle(double radius, double valuePerSquareUnit):
            Shape(valuePerSquareUnit),Radius(radius)
        {
        }
    
        virtual ~Circle()
        {
        }
    
        virtual double area() const
        {
            return PI * Radius * Radius;
        }
    };
    
    int main()
    {
        Rectangle* pr = new Rectangle(30, 20, 10);
        Circle* pc = new Circle(15, 10);
    
        //invoke Rectangle::area()
        printf("rectangle: area = %.2f, PerSquareUnit = %.2f, value = %.2f
    ", 
                    pr->area(), pr->getPerSquareUnit(), pr->value());
        //invoke Circle::area()
        printf("circle   : area = %.2f, PerSquareUnit = %.2f, value = %.2f
    ", 
                    pc->area(), pc->getPerSquareUnit(), pc->value());
        
        Shape* shape;
        shape = pr;
        printf("rectangle: area = %.2f, PerSquareUnit = %.2f, value = %.2f
    ", 
                    shape->area(), shape->getPerSquareUnit(), shape->value());
    
        shape = pc;
        printf("circle   : area = %.2f, PerSquareUnit = %.2f, value = %.2f
    ", 
                    shape->area(), shape->getPerSquareUnit(), shape->value());
    
        return 0;
    }

    编译一下看看。

     

    # g++ -o testVirtualFunc testVirtualFunc.cpp 
    /tmp/ccmGHV0C.o: In function `Shape::Shape(double)':
    testVirtualFunc.cpp:(.text._ZN5ShapeC2Ed[Shape::Shape(double)]+0x13): undefined reference to `vtable for Shape'
    /tmp/ccmGHV0C.o: In function `Shape::~Shape()':
    testVirtualFunc.cpp:(.text._ZN5ShapeD2Ev[Shape::~Shape()]+0x7): undefined reference to `vtable for Shape'
    /tmp/ccmGHV0C.o:(.rodata._ZTI9Rectangle[typeinfo for Rectangle]+0x8): undefined reference to `typeinfo for Shape'
    /tmp/ccmGHV0C.o:(.rodata._ZTI6Circle[typeinfo for Circle]+0x8): undefined reference to `typeinfo for Shape'
    collect2: ld returned 1 exit status

    实现基类Shapearea函数即可消除该编译错误。

    即将以下area函数

    virtual double area() const;

    改为

    virtual double area() const
    {
    }

    输出结果如下。

    # g++ -o testVirtualFunc testVirtualFunc.cpp 
    # ./testVirtualFunc
    rectangle: area = 600.00, PerSquareUnit = 10.00, value = 6000.00
    circle   : area = 706.86, PerSquareUnit = 10.00, value = 7068.58
    rectangle: area = 600.00, PerSquareUnit = 10.00, value = 6000.00
    circle   : area = 706.86, PerSquareUnit = 10.00, value = 7068.58

    2. 将基类Shape定义为纯虚类

    即将以下area函数

    virtual double area() const;

    改为

    virtual double area() const = 0;

    也可以实现该功能,其输出结果与上相同。

    3. 对该纯虚类的程序修改

    3.1 Shape类的构造函数中直接调用纯虚函数

    修改Shape的构造函数如下。

    Shape(double valuePerSquareUnit):
        ValuePerSquareUnit(valuePerSquareUnit)
    {
        std::cout << "creating shape, area = " << area() << std::endl;
    }

    则会在编译时出现如下错误:

    # g++ -o testPureVirtualFunc testPureVirtualFunc.cpp
    testPureVirtualFunc.cpp: In constructor ‘Shape::Shape(double)’:
    testPureVirtualFunc.cpp:18: warning: abstract virtual ‘virtual double Shape::area() const’ called from constructor
    /tmp/cc2WbWlw.o: In function `Shape::Shape(double)':
    testPureVirtualFunc.cpp:(.text._ZN5ShapeC2Ed[Shape::Shape(double)]+0x2c): undefined reference to `Shape::area() const'
    collect2: ld returned 1 exit status

    即不能在构造函数中直接调用纯虚函数。

     

    (Meyers, 3rd edition, Item 9: "Never call virtual functionsduring construction or destruction.") --in the third edition of Scott Meyers's "Effective C++"

    3.2 Shape类的构造函数中间接调用纯虚函数

    修改Shape的构造函数如下。

    Shape(double valuePerSquareUnit):
        ValuePerSquareUnit(valuePerSquareUnit)
    {
        //std::cout << "creating shape, area = " << area() << std::endl;
        std::cout << "creating shape, value = " << value() << std::endl;
    }

     

    其执行结果如下。

    # g++ -o testPureVirtualFunc testPureVirtualFunc.cpp
    # ./testPureVirtualFunc
    pure virtual method called
    terminate called without an active exception
    Aborted (core dumped)

    进入core dump文件调试,可以发现,是在value函数中调用area时出现该问题的。

    (gdb) bt
    #0  0x00110402 in __kernel_vsyscall ()
    #1  0x00a8e690 in raise () from /lib/libc.so.6
    #2  0x00a8ff91 in abort () from /lib/libc.so.6
    #3  0x06894ba0 in __gnu_cxx::__verbose_terminate_handler () from /usr/lib/libstdc++.so.6
    #4  0x06892685 in ?? () from /usr/lib/libstdc++.so.6
    #5  0x068926c2 in std::terminate () from /usr/lib/libstdc++.so.6
    #6  0x06892d65 in __cxa_pure_virtual () from /usr/lib/libstdc++.so.6
    #7  0x08048b00 in Shape::value (this=0x8261008) at testPureVirtualFunc.cpp:27
    #8  0x08048cd0 in Shape (this=0x8261008, valuePerSquareUnit=10) at testPureVirtualFunc.cpp:19
    #9  0x08048d44 in Rectangle (this=0x8261008, width=30, height=20, valuePerSquareUnit=10) at testPureVirtualFunc.cpp:49
    #10 0x08048920 in main () at testPureVirtualFunc.cpp:87

    3.3 为什么会出现"pure virtual functincalled"

     

    要想搞清楚这个问题,要思考这两件事情:

    a. 具体是在什么时刻调用了纯虚函数?

    b. 是谁打印的"pure virtual functioncalled"

     

    带着这两个问题,我们要深入调用内部,找到根本原因。调试过程如下。

    注意,编译时要加上"-g"选项,为调试准备符号。

    # g++ -g -o testPureVirtualFunc testPureVirtualFunc.cpp
    
    # gdb ./testPureVirtualFunc
    Copyright (C) 2006 Free Software Foundation, Inc.
    GDB is free software, covered by the GNU General Public License, and you are
    welcome to change it and/or distribute copies of it under certain conditions.
    Type "show copying" to see the conditions.
    There is absolutely no warranty for GDB.  Type "show warranty" for details.
    This GDB was configured as "i386-redhat-linux-gnu"...
    Using host libthread_db library "/lib/libthread_db.so.1".
    (gdb) l
    78          virtual double area() const
    79          {
    80              return PI * Radius * Radius;
    81          }
    82      };
    83
    84
    85      int main()
    86      {
    87          Rectangle* pr = new Rectangle(30, 20, 10);
    (gdb) b 87
    Breakpoint 1 at 0x80488e8: file testPureVirtualFunc.cpp, line 87.
    (gdb) r
    Starting program: /home/abo/linux/2009-12-28 testPureVirtualFuncCalled/testPureVirtualFunc 
    
    warning: Missing the separate debug info file: /usr/lib/debug/.build-id/ac/2eeb206486bb7315d6ac4cd64de0cb50838ff6.debug
    
    warning: Missing the separate debug info file: /usr/lib/debug/.build-id/88/27308433e33aeefb560f42fb133577c8936f20.debug
    
    warning: Missing the separate debug info file: /usr/lib/debug/.build-id/92/8ab51a53627c59877a85dd9afecc1619ca866c.debug
    
    warning: Missing the separate debug info file: /usr/lib/debug/.build-id/db/8cb95645d5df469d4aece301cdb5e60087be21.debug
    
    warning: Missing the separate debug info file: /usr/lib/debug/.build-id/ba/4ea1118691c826426e9410cafb798f25cefad5.debug
    
    Breakpoint 1, main () at testPureVirtualFunc.cpp:87
    87          Rectangle* pr = new Rectangle(30, 20, 10);
    (gdb) step
    Rectangle (this=0x909d008, width=30, height=20, valuePerSquareUnit=10) at testPureVirtualFunc.cpp:49
    49              Shape(valuePerSquareUnit),Width(width),Height(height)
    (gdb) p this
    $1 = (Rectangle * const) 0x909d008
    (gdb) p *this
    $2 = {<Shape> = {_vptr.Shape = 0x0, ValuePerSquareUnit = 0}, Width = 0, Height = 0}
    (gdb) info line Rectangle
    Line 42 of "testPureVirtualFunc.cpp" is at address 0x8048d08 <Rectangle> but contains no code.
    (gdb) step
    Shape (this=0x909d008, valuePerSquareUnit=10) at testPureVirtualFunc.cpp:16
    16              ValuePerSquareUnit(valuePerSquareUnit)
    (gdb) p this
    $3 = (Shape * const) 0x909d008
    (gdb) p *this
    $4 = {_vptr.Shape = 0x0, ValuePerSquareUnit = 0}
    (gdb) x/4 0x909d008
    0x909d008:      0x00000000      0x00000000      0x00000000      0x00000000
    (gdb) step
    19              std::cout << "creating shape, value = " << value() << std::endl;
    (gdb) x/4 0x909d008
    0x909d008:      0x08048fa8      0x00000000      0x40240000      0x00000000
    (gdb) p sizeof(ValuePerSquareUnit)
    $5 = 8
    (gdb) p sizeof(*this)
    $6 = 12
    (gdb) p *this
    $7 = {_vptr.Shape = 0x8048fa8, ValuePerSquareUnit = 10}
    (gdb) x/15i 0x8048fa8
    0x8048fa8 <_ZTV5Shape+8>:       xor    %al,0x8c6e0804(%edi)
    0x8048fae <_ZTV5Shape+14>:      add    $0x8,%al
    0x8048fb0 <_ZTV5Shape+16>:      cmp    $0x8c,%al
    0x8048fb2 <_ZTV5Shape+18>:      add    $0x8,%al
    0x8048fb4:      add    %al,(%eax)
    0x8048fb6:      add    %al,(%eax)
    0x8048fb8 <_ZTV6Circle>:        add    %al,(%eax)
    0x8048fba <_ZTV6Circle+2>:      add    %al,(%eax)
    0x8048fbc <_ZTV6Circle+4>:      int3   
    0x8048fbd <_ZTV6Circle+5>:      popl   (%eax,%ecx,1)
    0x8048fc0 <_ZTV6Circle+8>:      mov    %es:(%eax,%ecx,1),%eax
    0x8048fc4 <_ZTV6Circle+12>:     or     0x48bd808(%esp,%eax,1),%cl
    0x8048fcb <_ZTV6Circle+19>:     or     %ch,(%eax)
    0x8048fcd <_ZTI6Circle+1>:      movsl  %ds:(%esi),%es:(%edi)
    0x8048fce <_ZTI6Circle+2>:      add    $0x8,%al
    (gdb) step
    Shape::value (this=0x909d008) at testPureVirtualFunc.cpp:27
    27              return ValuePerSquareUnit * area();
    (gdb) x/4 0x909d008
    0x909d008:      test   $0x8f,%al
    0x909d00a:      add    $0x8,%al
    0x909d00c:      add    %al,(%eax)
    0x909d00e:      add    %al,(%eax)
    (gdb) x/4w 0x909d008
    0x909d008:      0x08048fa8      0x00000000      0x40240000      0x00000000
    (gdb) step
    pure virtual method called
    terminate called without an active exception
    
    Program received signal SIGABRT, Aborted.
    0x00110402 in __kernel_vsyscall ()
    (gdb) 

    我们可以通过C++filt命令对那些看不太懂的符号进行解析,

     

    #c++filt _ZTV5Shape
    vtablefor Shape
    #c++filt _ZTV6Circle
    vtablefor Circle

    具体的解释,我们可以查看 c++filt man 手册页。

     

    通过strace命令,我们可以清楚地看到程序在core dump之前调用write在标准输出中打印出"pure virtual method called",如下。

    # ./testPureVirtualFunc
    pure virtual method called
    terminate called without an active exception
    Aborted (core dumped)
    # strace ./testPureVirtualFunc

    其部分strace结果如下。

    mmap2(0xa65000, 1410608, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xa65000
    mmap2(0xbb8000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x153) = 0xbb8000
    mmap2(0xbbb000, 9776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xbbb000
    close(3)                                = 0
    mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f28000
    set_thread_area({entry_number:-1 -> 6, base_addr:0xb7f28af0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
    mprotect(0xbb8000, 8192, PROT_READ)     = 0
    mprotect(0xbe7000, 4096, PROT_READ)     = 0
    mprotect(0x68be000, 16384, PROT_READ)   = 0
    mprotect(0xa61000, 4096, PROT_READ)     = 0
    munmap(0xb7f29000, 71683)               = 0
    brk(0)                                  = 0x8410000
    brk(0x8431000)                          = 0x8431000
    write(2, "pure virtual method called
    ", 27pure virtual method called
    ) = 27
    write(2, "terminate called without an acti"..., 45terminate called without an active exception
    ) = 45
    rt_sigprocmask(SIG_UNBLOCK, [ABRT], NULL, 8) = 0
    gettid()                                = 25398
    tgkill(25398, 25398, SIGABRT)           = 0
    --- SIGABRT (Aborted) @ 0 (0) ---
    +++ killed by SIGABRT (core dumped) +++

    Reference

    http://www.artima.com/cppsource/pure_virtual.html

    http://www.codeproject.com/KB/cpp/PureVirtualFunctionCall.aspx

    http://blog.csdn.net/kikikind/archive/2008/07/13/2645316.aspx

    http://www.cnblogs.com/whjiang/archive/2007/10/22/932880.html

    http://support.microsoft.com/kb/120919/zh-cn

    http://support.microsoft.com/kb/120919/en-us/

  • 相关阅读:
    桶排序
    linux下如何修改进程优先级?
    在旋转排序数组之后的数组中找到目标值的位置(很多遍 ,总是晕)
    PHP生成随机数函数rand(min,max)
    使用PHP编写发红包程序
    maven的lifecycle
    mysq中char,varchar,text的区别
    contrller层的编码设设计流程以及详细配置
    mybatis的dao层和service层的编码设计的配置
    Next_day()函数的用法
  • 原文地址:https://www.cnblogs.com/pangblog/p/3239116.html
Copyright © 2020-2023  润新知