代码之家  ›  专栏  ›  技术社区  ›  Sadique

虚函数和类

c++
  •  -2
  • Sadique  · 技术社区  · 14 年前

    我需要一些基本问题的答案。我又迷路了。:(

    问题1-此声明是否有效:
    Whenever we define the function to be pure virtual function, this means that function has no body.

    q2-动态绑定的概念是什么?我的意思是,如果编译器使用vtables和vptrs优化代码,那么它是如何运行时多态性的?

    问题3-vtables和vptr是什么?它们的大小如何变化?

    Q4-请参见此代码:

    class base
    {
        public:
            virtual void display()
            {
                cout<<"Displaying from base";
            }
    };
    
    class derived:public base
    {
        public:
            void display(){cout<<"\nDisplaying from derived";}
    };
    
    int main()
    {
        base b,*bptr;
        derived d;
        bptr=&b;
        bptr->display();
        bptr=&d;
        bptr->display();
    } 
    

    输出:

    Displaying from base
    Displaying from derieved
    

    请有人回答为什么基类指针可以指向派生类的成员函数,反之亦然是不可能的,为什么?

    4 回复  |  直到 14 年前
        1
  •  4
  •   Community CDub    8 年前
    1. 错误的。它只是意味着任何派生类都必须实现所述函数。您仍然可以为函数提供定义,并且可以通过 Base::Function() *

    2. 虚拟表是实现虚拟函数的一种方法。(不过,标准并不规定这是方法。)当进行多态调用时,编译器将在函数表中查找函数并调用它,从而启用运行时绑定。(表在编译时生成。)

    3. 见上文。它们的大小随着虚拟功能的增加而变化。但是,实例不存储表,而是存储指向表的指针,因此类大小只增加了一个大小。

    4. Sounds like you need a book.

    *这里有一个典型的例子:

    struct IBase
    {
        virtual ~IBase(void) = 0;
    };
    
    inline IBase::~IBase(void) {}
    

    这不是没有纯虚拟函数的抽象类,但析构函数需要定义(因为当派生类析构函数时将调用它)。

        2
  •  2
  •   mukeshkumar    14 年前

    1)不一定。有时您为纯虚拟函数提供主体

    2)要调用的函数在运行时确定。

        3
  •  1
  •   David Rodríguez - dribeas    14 年前
    1. 错误的。它只意味着派生类必须实现该方法,并且该级别的方法定义(如果存在)不会被视为对虚拟方法的重写。
    2. vtable在编译时实现,但在运行时使用。编译器将通过vtable重定向调用,这取决于对象的运行时类型(指向base的指针具有静态类型 base* ,但可能指向类型为的对象 derived 在运行时)。
    3. vptr是指向vtable的指针,它们不会更改大小。vTables是指向代码(可能指向方法或某些适配器代码)的指针表,在类中声明的每个虚拟方法都有一个条目。

    在代码中编辑之后:

    指针指向类型为的对象 base 在第一次调用期间,但它指向类型为的对象 衍生的 在第二个呼叫位置。动态调度机制(vtable)将调用路由到适当的方法。

    一个常见的实现可能有助于您理解,在声明虚拟函数的每个类中,编译器为指向虚拟表的指针保留空间,并且它还生成虚拟表本身,在其中向每个虚拟方法的定义添加指针。对象的内存布局只有那个额外的指针。

    当派生类重写任何基类方法时,编译器将生成一个不同的vtable,其中包含指向该级别的最终重写器的指针。基类和派生类的内存布局在 基础 子对象部分(通常是开始部分),但 基础 对象将指向基vtable,而vptr的值位于 衍生的 对象将指向派生的vtable。

    当编译器看到类似 bptr->display() ,它检查基类的定义,并发现它是第一个虚拟方法,然后将调用重定向为: bptr->hidden_vptr[0]() . 如果指针引用的是实基实例,则该实例将是指向 base::display ,而在 衍生的 它将指向的实例 derived::display .

    注意,这个答案中有很多人在挥手。所有这些都是实现定义的(语言没有指定调度机制),在大多数情况下,调度机制更复杂。例如,当发生多个继承时,vtable不会直接指向最终的重写器,而是指向一个适配器代码块,该代码块重置第一个隐式 this 参数偏移,如 基础 除了第一个基对象之外,所有子对象都与内存中最派生的对象不一致——这远远超出了问题的范围,请记住,这个答案是一个粗略的想法,并且在实际系统中增加了复杂性。

        4
  •  1
  •   ChrisW    14 年前

    这个声明有效吗

    不完全是:它可能有一具尸体。更准确的定义是“每当我们将一个方法定义为纯虚拟的时候,这意味着必须在具体的子类中定义(重写)该方法。”

    动态绑定的概念是什么?我的意思是,如果编译器使用vtables和vptrs优化代码,那么它是如何运行时多态性的?

    如果在运行时有一个超类(例如形状)的实例,则不必/不必知道它是哪个子类(例如圆或正方形)。

    vtables和vptr是什么?它们的大小如何变化?

    每个类有一个vtable(对于具有一个或多个虚拟方法的任何类)。vtable包含指向类的虚拟方法地址的指针。

    每个对象有一个vptr(对于具有一个或多个虚拟方法的任何对象)。vptr指向对象类的vtable。

    vtable的大小随着类中虚拟函数的数目而增加。vptr的大小可能是常量。

    请有人回答为什么基类指针可以指向派生类的成员函数,反之亦然是不可能的,为什么?

    如果您想调用基类函数,那么(因为它是虚拟的,而虚拟的默认行为是通过vptr/vtable调用最派生的版本),那么您必须这样明确地说,例如:

    bptr->base::display();