精华内容
下载资源
问答
  • CD 子类必须重载父类里的虚函数 子类必须重载父类里的纯虚函数 虚基类的构造函数在非虚基类之前调用(最终继承类...子类实现父类虚函数叫重写,叫重载; 父类有纯虚函数,子类可以实现,此时子类仍是抽象类。 ...

    CD
    子类必须重载父类里的虚函数
    子类必须重载父类里的纯虚函数
    虚基类的构造函数在非虚基类之前调用(最终继承类构造函数中,成员初始化列表同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数执行)
    带有纯虚函数的类不能直接实例化

    子类实现父类虚函数叫重写,不叫重载;
    父类有纯虚函数,子类可以不实现,此时子类仍是抽象类。

    展开全文
  • 1,从存储空间角度 虚函数对应一...所以构造函数能是虚函数。 vtbl是在编译期就建立了,各个虚函数这时被组织成了一个虚函数的入口地址的数组.而对象的隐藏成员--vptr是在运行期--也就是构造函数被调用时进行初...

    1,从存储空间角度

       虚函数对应一个虚表vtbl,可是这个vtbl其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtbl来调用,可是对象还没有实例化,也就是内存空间还没有,无法找到vtbl。所以构造函数不能是虚函数。

      vtbl是在编译期就建立了,各个虚函数这时被组织成了一个虚函数的入口地址的数组.而对象的隐藏成员--vptr是在运行期--也就是构造函数被调用时进行初始化的,这是实现多态的关键。

    2,从使用角度

      虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用。构造函数本身就是要初始化实例,那使用虚函数也没有实际意义呀。所以构造函数没有必要是虚函数。

    虚函数的作用在于通过父类的指针或者引用来调用它的时候能够变成调用子类的那个成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数

    3、构造函数不需要是虚函数,也不允许是虚函数,因为创建一个对象时我们总是要明确指定对象的类型,尽管我们可能通过实验室的基类的指针或引用去访问它。但析构却不一定,我们往往通过基类的指针来销毁对象。这时候如果析构函数不是虚函数,就不能正确识别对象类型从而不能正确调用析构函数。

    4、从实现上看,vtbl在构造函数调用后才建立,因而构造函数不可能成为虚函数  

      从实际含义上看,在调用构造函数时还不能确定对象的真实类型(因为子类会调父类的构造函数);而且构造函数的作用是提供初始化,在对象生命期只执行一次,不是对象的动态行为,也没有太大的必要成为虚函数

    5、当一个构造函数被调用时,它做的首要的事情之一是初始化它的vptr。因此,它只能知道它是“当前”类的,而完全忽视这个对象后面是否还有继承者。 当编译器为这个构造函数产生代码时,它是为这个类的构造函数产生代码- -既不是为基类,也不是为它的派生类(因为类不知道谁继承它)。

      所以它使用的vptr必须是对于这个类的vtbl。而且,只要它是最后的构造函数调用,那么在这个对象的生命期内,vptr将 保持被初始化为指向这个vtbl, 但如果接着还有一个更晚派生的构造函数被调用,这个构造函数又将设置vptr指向它的vtbl,等.直到最后的构造函数结束。vptr的状态是由被最后调用的构造函数确定的。这就是为什么构造函数调用是从基类到更加派生类顺序的另一个理由。

      但是,当这一系列构造函数调用正发生时,每个构造函数都已经设置vptr指向它自己的vtbl。如果函数调用使用虚机制,它将只产生通过它自己的vtbl的调用,而不是最后的vtbl(所有构造函数被调用后才会有最后的vtbl)。


    基类析构函数只能是虚函数:

    派生类对象构造的时候先调用基类的构造函数再调用派生类的构造函数,析构的时候先调用派生类析构函数再调用基类析构函数。

    其实这个很好理解,派生类的成员由两部分组成,一部分是从基类那里继承而来,一部分是自己定义的。那么在实例化对象的时候,首先利用基类构造函数去初始化从基类继承而来的成员,再用派生类构造函数初始化自己定义的部分。

    同时,不止构造函数派生类只负责自己的那部分,析构函数也是,所以派生类的析构函数会只析构自己的那部分,这时候如果基类的析构函数不是虚函数,则只能调用基类的析构函数析构从基类继承来的那部分成员,而派生类自己的成员没有被析构,所以就会出现只删一半的现象,造成内存泄漏。

    所以析构函数要定义成虚函数。

    用基类指针指向派生类时

    在基类析构函数声明为virtual的时候,delete基类指针,会先调用派生类的析构函数,由于继承关系再调用基类的析构函数。

    在基类析构函数没有声明为virtual的时候,delete基类指针,只会调用基类的析构函数,而不会调用派生类的析构函数,这样会造成销毁对象的不完全。

    个人举例:Base *p=new Drived;

    //new 过程,会先调用operator new分配空间,再调用Dirived的constructor,在调用Dirived的constructor时,会先调用Bass的constructor再调用Dirived的constructor,故delete p时,(在Bass的destructor是virtual时)会先调用Dirived的destructor再调用Bass的destructor


    展开全文
  • 常见的能声明为虚函数的有:普通函数(非成员函数);静态成员函数;内联成员函数;构造函数;友元函数。 1.为什么C++支持普通函数为虚函数? 普通函数(非成员函数)只能被overload,能被override,声明为...

    常见的不能声明为虚函数的有:普通函数(非成员函数);静态成员函数;内联成员函数;构造函数;友元函数。

    1.为什么C++不支持普通函数为虚函数?

    普通函数(非成员函数)只能被overload,不能被override,声明为虚函数也没有什么意思,因此编译器会在编译时邦定函数。

    2.为什么C++不支持构造函数为虚函数?

    这个原因很简单,主要是从语义上考虑,所以不支持。因为构造函数本来就是为了明确初始化对象成员才产生的,然而virtual function主要是为了再不完全了解细节的情况下也能正确处理对象。另外,virtual函数是在不同类型的对象产生不同的动作,现在对象还没有产生,如何使用virtual函数来完成你想完成的动作。(这不就是典型的悖论)

    3.为什么C++不支持内联成员函数为虚函数?

    其实很简单,那内联函数就是为了在代码中直接展开,减少函数调用花费的代价,虚函数是为了在继承后对象能够准确的执行自己的动作,这是不可能统一的。(再说了,inline函数在编译时被展开,虚函数在运行时才能动态的邦定函数)

    4.为什么C++不支持静态成员函数为虚函数?

    这也很简单,静态成员函数对于每个类来说只有一份代码,所有的对象都共享这一份代码,他不归某个具体对象所有,所以他也没有要动态邦定的必要性。

    5.为什么C++不支持友元函数为虚函数?

    因为C++不支持友元函数的继承,对于没有继承特性的函数没有虚函数的说法。
     

    展开全文
  • 虚函数可以是内联函数

    千次阅读 2015-07-30 09:34:59
    虚函数可以是内联函数 内联函数能为虚函数,原因在于虚表机制需要一个真正的函数地址,而内联函数展开以后,就不是一个函数,而是一段简单的代码(多数C++对象模型使用虚表实现多态,对此标准提供支持),...

    虚函数可不可以是内联函数

    内联函数不能为虚函数,原因在于虚表机制需要一个真正的函数地址,而内联函数展开以后,就不是一个函数,而是一段简单的代码(多数C++对象模型使用虚表实现多态,对此标准提供支持),可能有些内联函数会无法内联展开,而编译成为函数。

    class Foo
    {
    public:
     virtual void vFunc() const;
    private:
    };


    class Child: public Foo
    {
    public:
     virtual void vFunc() const;
    };


    inline
    void Foo::vFunc() const
    {cout<<"class Foo"<<endl;}

    inline
    void Child::vFunc() const
    {
     cout<<"class Child"<<endl;
    }

     

    int main()
    {
     Foo* pCh = new Child;
     pCh->vFunc();
     
     return 0;
    }

     

    输出为Child!即使虚函数是内联函数仍然正确的动态绑定!

    原因是:即使虚函数被声明为内联函数,编译器遇到这种情况根本不会把这样的函数内联展开,而是当做普通函数来处理。


    虚函数什么情况下会内联

    最近有个朋友研究了一下虚函数内联的问题,我也对这个问题产生了兴趣,发现网上也有好多讨论这个问题的,众说纷纭,于是花了点时间研究了一下。
    自己在vc下做了一些实验,通过观察各种情况下汇编代码,以及了一些资料的记载得出以下结论:

    虚函数是可以内联的

    据我所知有3种情况会内联展开:

    1.“虚函数静态调用”,像这样
    pclass->class::vf()
    或者在类体内调用class::vf() //相当于this->class::vf()
    这种调用方式其实类似第二种调用方式。
    如果你可以肯定你要调用的函数,并且这个函数不需要运行时刻确定的数据。就可以这样写来提高效率。不错吧:)

    2.用一个类对象通过成员选择符.调用虚函数,如
    obj.vf()
    这时虚函数vf()就可以被vc优化内联展开。
    这样调用等于告诉编译器你要调用的具体函数,在函数有inline修饰或是体内定义的情况下就会被内联展开。

    3.虚析构函数的静态调用链一定会被内联展开(Lippman文章提到的,见后附文章)

    不会内联的情况:
    用类指针调用虚函数如
    pclass->vf()
    这时vf()无论如何不会被内联展开。(即便使用__forceinline关键字强制内联也不行,最终他还是一个非inline函数。)
    通过类指针调用虚函数,由于虚函数的多态性,那就意味着你在告诉编译器,这是一个需要在运行时“动态”调用的函数,不要把它内联。

    ----------------------------------------------------------------------------------

    相关资料:

    《Effective C++》的第33款:
    即使是最简单的虚函数调用,编译器的内联处理程序对它也爱莫能助。(这一点也不奇怪。virtual的意思是"等到运行时再决定调用哪个函数",inline的意思是"在编译期间将调用之处用被调函数来代替",如果编译器甚至还不知道哪个函数将被调用,当然就不能责怪它拒绝生成内联调用了)。

    这个结论显然是错的,Lippman的文章里说的很清楚了,至少虚析构函数静态调用链一定会被内联。
    但是我清楚记得这本书的另一个条款很清楚的说明了关于虚函数内联的实际情况,简直和这里的说法自相矛盾。(时间久了具体记不清是哪一款了)所以我认为《Effective C++》的作者本人应该十分清楚关于虚函数内联的实际情况,如果你仔细看完他的书应该也不会被误导,这里之所以这样写也许只是他的一时笔误,或者作者觉得没必要在这里提到那些“特殊的”情况吧。

    ----------------------------------------------------------------------------------

    然后,在《Inside the C++ Object Model》
    第五章Presence of a Virtual Specification节
    末尾有这样一段话

    However, couldn't a compiler, through analysis, determine that only a single instance of the function exists within the class hierarchy. In so doing, could it not transform the call into a static invocation, thereby allowing for the inline expansion of the call? But what happens if the hierarchy is subsequently added to and the new class introduces a new instance of the function? This new class invalidates the optimization. The function must now be recompiled (or perhaps a second, polymorphic instance generated, with the compiler determining through flow analysis which instance needs to be invoked). The function, however, may exist as a binary within a library. Unearthing this dependency is likely to require some form of persistent program database or library manager.
    大概意思说如果编译器把虚函数调用转化成静态调用并内联展开,一旦类层次体系改变,加入新的类就会破坏这种优化。而函数有可能以二进制形式存在于库中,编译器很难挖掘出这样的相依性。

    ----------------------------------------------------------------------------------

    最后,这篇最重要的文章
    Standard C++ Programming: Virtual Functions and Inlining

    Josée Lajoie and Stanley Lippman
    ] 01, 2000

    ----------------------------------------------------------------------------------

    [This is the last installment of a column that was being published in C++ Report magazine. Since the magazine ceased publication before this installment could be published, Josée Lajoie and Stan Lippman were gracious enough to let us publish it on the CUJ website. — mb]

     

    曾经,我们常常在谈及C++时听到一个问题:“虚函数真的应该被申明为内联吗?”现在,我们很少再听到这个问题了。反过来,我们现在听到的是“你不应该将print()函数内联。将虚函数申明为内联是错误的。”

    这么说有两个主要理由:(1)虚函数是在运行期判决的,而内联是编译期行为,所以不能从这个(内联)申明上得到任何好处;(2)将虚函数申明为内联将造成此函数在可执行文件中有多份拷贝,因此我们为一个无论如何都不能内联的函数付出了在空间上的处罚(WQ注,所谓的内联函数非内联问题)。显然没脑子。

    只是它并不真的正确。反思一下理由(1):在很多情况下,虚函数是静态判决的--尤其是派生类的虚函数调用它的基类版本时。为什么会那么做?封装。一个很好的例子是析构函数的静态调用链:基类的析构函数被派生类的析构函数触发。除了最初的一个外,所有的析构函数的调用都是被静态判决的。不让基类的虚析构函数内联,就不能从中获益。这会造成很大的差别吗?如果继承层次很深,而又有大量的对象需要析构,(答案是)“是的”。

    另外一个例子不涉及析构函数。想像我们正在设计一个图书馆出借管理程序。我们已经将“位置”放入抽象类LibraryMaterial。当申明print()函数为纯虚函数时,我们也提供其定义:打印出对象的位置。

    class LibraryMaterial {

    private:

        MaterialLocation _loc; // shared data

        // ...

     

    public:

        // declares pure virtual function

        inline virtual void print( ostream& = cout ) = 0;

    };

     

    // we actually want to encapsulate the handling of the

    // location of the material within a base class

    // LibraryMaterial print() method - we just don’t want it

    // invoked through the virtual interface. That is, it is

    // only to be invoked within a derived class print() method

     

    inline void

    LibraryMaterial::

    print( ostream &os ) { os << _loc; }

     

     

    接着引入Book类;它的print()函数会输出书名、作者等等。在此之前,它先调用基类的LibraryMaterial::print()函数以显示位置信息。例如:

    inline void

    Book::

    print( ostream &os )

    {

          // ok, this is resolved statically,

          // and therefore is inline expanded ...

          LibraryMaterial::print();

           

          os << "title:" << _title

             << "author" << _author << endl;

    }

    AudioBook类从Book派生,引入了一个二选一的借出策略,并且加入了一些附加信息,比如讲解员、格式等等。这些都将在它的print()函数中显示出来。在显示这些以前,它先调用Book::print():

     

    inline void

    AudioBook::

    print( ostream &os )

    {

          // ok, this is resolved statically,

          // and therefore is inline expanded ...

          Book::print();

          os << "narrator:" << _narrator << endl;

    }

    在这个例子和析构函数的例子中,派生类的虚方法递增式地扩展其基类版本的功能,并以调用链的方式被调用,只有最初一次调用是由虚体系决定的。这个没有被命名的继承树设计模式,如果从不将虚函数申明为内联的话,显然会有些低效。

    关于理由(2)的代码膨胀问题怎么说?好吧,思考一下。如果写出,

    LibraryMaterial *p =

        new AudioBook( "Mason & Dixon",

                       "Thomas Pynchon", "Johnny Depp" );

    // ...

    p->print();

    此处的print()会内联吗?不,当然不会。这必须在运行期经过虚体系的判决。Okay。它会导致此处的print()函数有它自己的定义体吗?也不会。调用被编译为类似于这种形式:

    // Pseudo C++ Code

    // Possible transformation of p->print()

    ( *p->_vptr[ 2 ] )( p );

    那个2是print()函数在相应的虚函数表中的位置。因为这个对print()的调用是通过函数指针_vptr[2]进行的,编译器不能静态决定被调用函数的位置,并且函数不能被内联。

    当然,内联的虚函数print()的定义必须出现在可执行文件中的某处,代码才能正确执行。也就是说,至少需要一个定义体,以便将它的地址放入虚函数表。编译器如何决定何时产生那一个定义体的呢?一个实现策略是在产生那类的虚函数表时同时产生那个定义体。这意味着针对为一个类所生成的每个虚函数表实例,每个内联的虚函数的一个实例也被产生。

    在可执行文件中,为一个类产生的虚函数表,实际上有多少个?啊,很好,问得好。C++标准规定了虚函数在行为上的要求;但它没有规定实现虚函数上的要求。既然虚函数表的存在不是C++标准所要求的,明显标准也没有进一步要求如何处理虚函数表以及生成多少次。最佳的数目当然是“一次”。例如,Stroustrup的原始cfront实现版本,在大部份情况下聪明地达成了这一点。 (Stan和Andy Koenig描述了其算法,发表于1990年3月,C++ Report,“Optimizing Virtual Tables in C++ Release 2.0.”)

    此外,C++标准现在要求内联函数的行为要满足好象程序中只存在一个定义体,即使这个函数可能被定义在不同的文件中。新的规则是说满足规定的实现版本,行为上应该好象只生成了一个实例。一旦标准的这一点被广泛采用,对内联函数潜在的代码膨胀问题的关注应该消失了。

    C++社群中存在着一个冲突:教学上需要规则表现为简单的检查表vs实践中需要明智地依据环境而运用规则。前者是对语言的复杂度的回应;后者,是对我们构造的解决方案的复杂度的回应。何时将虚函数申明为内联的问题,是这种冲突的一个很好的例证。

     

    About the Authors
    Stanley Lippman was the software Technical Director for the Firebird segment of Disney's Fantasia 2000. He was recently technical lead on the ToonShooter image capture and playback system under Linux for DreamWorks Feature Animation and consulted with the Jet Propulsion Laboratory. He is currently IT Training Program Chair for You-niversity.com, an e-learning training company. He can be reached at stanleyl@you-niversity, www.you-niversity.com, and www.objectwrite.com.

    Josée Lajoie is currently doing her Master's degree in Computer Graphics at the University Waterloo. Previously, she was a member of the C/C++ compiler development team at the IBM Canada Laboratory and was the chair of the core language working group for the ANSI/ISO C++ Standard Committee. She can be reached at jlajoie@cgl.uwaterloo.ca.

    s
    展开全文
  • 关于虚函数

    2015-08-25 23:24:48
    2.(int*)*(int*)(*aObj)得到虚函数表指针vptr 指针的类型是(int*),这时需要转化下 3.vptr是指向vptr table,其实就是指向一个数组,数组元素是函数指针。   (int*)*(int*)(*aObj) 指向数组第一个元素 ...
  • 虚函数

    2014-07-19 10:56:45
    9、下列关于虚函数说法正确的是() A、在构造函数中调用类自己的虚函数虚函数的动态绑定机制还会生效。 B、在析构函数中调用类自己的虚函数虚函数的动态绑定机制还会生效。 C、静态函数可以是虚函数 ...
  • 构造函数能声明为虚函数,而析构函数可以声明为虚函数,在有的情景下析构函数必须声明为虚函数建议在构造函数和析构函数里调用虚函数。 构造函数能声明为虚函数的原因? 构造一个对象时,必须知道对象...
  • 虚函数的定义:  虚函数必须是类的非静态成员函数(且非构造函数),其访问权限是public(可以定义为private or proteceted, 但是对于多态来说,没有意义。),在基类的类定义中定义虚函数的一般形式:  virtual ...
  • 2.只有类的成员函数才能说明为虚函数,因为虚函数仅适合用与有继承关系的类对象,所以普通函数能说明为虚函数。3.静态成员函数能是虚函数,因为静态成员函数的特点是受限制于某个对象。4.内联(inline)函数...
  • 最近有人问构造函数能能是虚函数: 当然能 解释一下: 1,从存储空间角度  虚函数对应一个vtable,这大家都知道,可是这个vtable其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,...
  • 常见的不不能声明为虚函数的有:普通函数(非成员函数);静态成员函数;内联成员函数;构造函数;友元函数。 1、为什么C++支持普通函数为虚函数?  普通函数(非成员函数)只能被overload,能被override,...
  • 构造函数为什么能是虚函数 C++ 从存储空间角度,虚函数对应一个指向vtable虚函数表的指针,这大家都知道,可是这个指向vtable的指针其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就...
  •  那么构造函数是在建立对象的时候,在栈里面申请一段区间用于存放对象中的内容(包括vptr和数据内容),拥有这个vptr之后,才能找到虚函数的物理存储空间,所以如果构造函数是虚函数,那么根本找到具体实现的
  • 常见的不不能声明为虚函数的有:普通函数(非成员函数);静态成员函数;内联成员函数;构造函数;友元函数。 1、为什么C++支持普通函数为虚函数? 普通函数(非成员函数)只能被overload,能被override,声明...
  • 一道虚函数的选择题

    千次阅读 2015-03-20 21:20:59
    下列关于虚函数说法正确的是() A、在构造函数中调用类自己的虚函数虚函数的动态绑定机制还会生效。 B、在析构函数中调用类自己的虚函数虚函数的动态绑定机制还会生效。 C、静态函数可以是虚函数 D、虚函数...
  • 关于c++虚函数

    2012-12-27 00:28:45
    C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。...
  • C++虚函数虚函数

    2017-10-07 21:59:21
    虚函数的定义:  虚函数必须是类的非静态成员函数(且非构造函数),其访问权限是public(可以定义为private or proteceted, 但是对于多态来说,没有意义。),在基类的类定义中定义虚函数的一般形式:  virtual ...
  • C++ 虚函数

    2014-09-19 17:01:02
    C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。...关于虚函数的使用方法,我在这里做过多的
  • 虚函数的定义:  虚函数必须是类的非静态成员函数(且非构造函数),其访问权限是public(可以定义为private or proteceted, 但是对于多态来说,没有意义。),在基类的类定义中定义虚函数的一般形式:  virtual ...
  • c++虚函数虚函数

    2012-07-31 15:21:00
     对于此文中关于单继承虚函数的重载覆盖问题,我认为不会产生覆盖,因为单继承中子类和父类共用一张虚函数表,而且所有的父类和子类的实例对象都共用一张虚函数表,如果产生覆盖,那么父类定义的虚函数将会丢失。...
  • 虚函数虚函数表、虚继承

    千次阅读 2014-04-10 21:03:29
    1、虚函数 ...虚函数的定义:  ... 虚函数必须是类的非静态成员函数(且非构造函数),其访问权限是public(可以定义为privateor proteceted, 但是对于多态来说,没有意义)...(1)非类的成员函数能定义为虚函数
  • C++虚函数

    2017-08-22 17:35:57
    本身存在虚函数不严谨但存在基类虚函数覆盖的单继承类的内存布局 定义了基类没有的虚函数的单继承的类对象布局 多继承且存在虚函数覆盖同时又存在自身定义的虚函数的类对象布局 如果第1个直接基类没有虚函数表 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,769
精华内容 3,507
关键字:

关于虚函数的说法不正确的是