C++——虚函数问题小集

学习C++ 不可避免地会遇到虚函数的问题,下面几个问题在学习初期或多或少会存在一些疑惑,所以便将其总结了下来。

 

1.为什么静态成员函数、构造函数不能定义为虚函数?

  因为静态成员函数是一个大家共享的一个资源,它其实就是一个“受类域限定符限制”的普通函数,没有this指针,不需要对象就可以调用;而虚函数是实打实的成员函数,调用依赖于创建的实例(编译时要把实例的地址给该成员函数的this指针) 所以一个依赖对象,一个则不,两者是是相矛盾的.
  
  对构造函数来说,因为在用运行构造函数来构造实例的时候,实例还未创建完成,而虚函数的运行是建立在对象构建完备的基础上,所以将构造函数定义为虚函数是行不通的。
 
 

2.为什么在构造、析构函数中不要调用virtual函数?

  直白的说是由于调用的虚函数达不到虚函数的效果,而实现虚函数有代价,结果就是费力不讨好。 

不妨考虑下面一个例子

 

class Base
{
public:
    Base()
    {
        vfunc();
    }

    virtual void vfunc()
    {
        cout<<"Base::vfunc()"<<endl;
    }
};
class Derive : public Base
{
public:
    Derive()
        :Base()
        ,_pData(new int(2)) 
    { 
        vfunc();
    }

    virtual void vfunc()
    {
        cout<<"Derive::vfunc() "<<*_pData<<endl;
    }

    ~Derive() { delete _pData;}
private:
    int* _pData;
};
void Test()
{
    Derive d;
}
例子

 

结果是这样: 

 

我们都知道在构造子类对象d的时候,会先去构造d中基类成份,即去调用基类构造函数;但现在这里的虚函数好像构成重写,那么输出怎么不是这样呢?

Derive::vfunc() 2
Derive::vfunc() 2

但是这样答案明显是错的!!前面的这个‘2’不可能出现,它这不还没初始的嘛。

 

现在发现了问题,拿effectiveC++观点来讲就是在base class构造期间,virtual函数不是virtual函数”,这里vfunc()没有达到虚函数的效果。

原因就是构造基类成份时d中虚表中尚未注册派生类的VFunc(),这时便只能调用基类的VFunc();所以这类虚函数调用是不会下降到子类中。退一步讲,假使下降到子类中,那也会出现上面那种访问子类中未初始化成员_pData,致使程序崩溃的情况。所以干嘛还要在构造函数当中调用它呢?又没有虚函数效果,还影响效率。
  

 

对于析构函数,道理同样是如此。派生类部分先析构,然后析构基类部分,但此时只能调用基类自身的函数。倘若一旦让派生类析构函数再执行,对象内派生类成员变量(如_pData)便呈现未定义的值,导致未知的行为。(如果将基类改为纯虚函数,并在构造、析构中调用,此时编译器便不会“安分”了)

所以,不要在构造、析构函数中调虚函数,它带不来预想的结果(多态),就算有也是一张通往“彻夜调试大会串的直达车票”

 

 

 

3.什么情况下要将基类的析构函数声明为虚函数?

  答:用一个基类指针或引用来释放派生类对象时,建议将析构写为虚函数。

  编译器总是根据类型来调用类成员函数。但是一个派生类的指针可以安全地转化为一个基类的指针。这样删除一个基类的指针的时候,C++不管这个指针指向一个 基类对象还是一个派生类的对象,调用的都是基类的析构函数而不是派生类的;所以这样继承类的成分没有被销毁,造成局部销毁对象,形成资源泄漏。

所以建议将析构函数声明为虚函数。这样就实现了多态,避免内存泄漏     

      

注:

  ①基类中函数一旦声明为虚函数,不管子类是否加上virtual,子类中形式相同函数继续保持虚函数特性。

  ②析构函数十分特殊,由于基类和子类的析构它们底层其实是同名的(destructor),所以会构成覆盖

 


    虽然虚函数有好处,但C++不 把虚析构函数直接作为默认值。原因就是是虚函数表的开销以及和C语言的类型的兼容性。有虚函数的对象总是在开始的位置包含一个隐含的虚函数表指针成员。所以,如果我们设计的类不涉及继承关系时,不要将析构搞成虚函数,没有必要。

 

posted @ 2018-05-03 15:45  tp_16b  阅读(680)  评论(0编辑  收藏  举报