码迷,mamicode.com
首页 > 其他好文 > 详细

虚函数

时间:2015-09-28 16:10:03      阅读:215      评论:0      收藏:0      [点我收藏+]

标签:

析构函数什么情况下要定义为虚函数?

1.第一段代码

#include<iostream>
using namespace std;
class ClxBase {
public:
    ClxBase() {};
    ~ClxBase() {
        cout << "Output from the destructor of class ClxBase!" << endl;
    };
    void DoSomething() {
        cout << "Do something in class ClxBase!" << endl;
    };
};
class ClxDerived : public ClxBase {
public:
    ClxDerived() {};
    ~ClxDerived() {
        cout << "Output from the destructor of class ClxDerived!" << endl;
    };
    void DoSomething() {
        cout << "Do something in class ClxDerived!" << endl;
    };
};
int   main() {
    ClxDerived *p =  new ClxDerived;
    p->DoSomething();
    delete p;
    return 0;
}

运行结果:

Do something in class ClxDerived!
Output from the destructor of class ClxDerived!
Output from the destructor of class ClxBase!

 

这段代码中基类的析构函数不是虚函数,在main函数中用继承类的指针去操作继承类的成员,释放指针P的过程是:先释放继承类的资源,再释放基类资源. 

2.第二段代码

#include<iostream>
using namespace std;
class ClxBase {
public:
    ClxBase() {};
    ~ClxBase() {
        cout << "Output from the destructor of class ClxBase!" << endl;
    };
    void DoSomething() {
        cout << "Do something in class ClxBase!" << endl;
    };
};
class ClxDerived : public ClxBase {
public:
    ClxDerived() {};
    ~ClxDerived() {
        cout << "Output from the destructor of class ClxDerived!" << endl;
    };
    void DoSomething() {
        cout << "Do something in class ClxDerived!" << endl;
    }
};
int   main() {
    ClxBase *p =  new ClxDerived;
    p->DoSomething();
    delete p;
    return 0;
}

输出结果:

Do something in class ClxBase!
Output from the destructor of class ClxBase!

 

这段代码中基类的析构函数同样不是虚函数,不同的是在main函数中用基类的指针去操作继承类的成员,释放指针P的过程是:只是释放了基类的资源,而没有调用继承类的析构函数.调用dosomething()函数执行的也是基类定义的函数.

一般情况下,这样的删除只能够删除基类对象,而不能删除子类对象,形成了删除一半形象,造成内存泄漏.

在公有继承中,基类对派生类及其对象的操作,只能影响到那些从基类继承下来的成员.如果想要用基类对非继承成员进行操作,则要把基类的这个函数定义为虚函数.

析构函数自然也应该如此:如果它想析构子类中的重新定义或新的成员及对象,当然也应该声明为虚的.

3.第三段代码:

#include<iostream>
using namespace std;
class ClxBase {
public:
    ClxBase() {};
    virtual ~ClxBase() {
        cout << "Output from the destructor of class ClxBase!" << endl;
    };
    virtual void DoSomething() {
        cout << "Do something in class ClxBase!" << endl;
    };
};
class ClxDerived : public ClxBase {
public:
    ClxDerived() {};
    ~ClxDerived() {
        cout << "Output from the destructor of class ClxDerived!" << endl;
    };
    void DoSomething() {
        cout << "Do something in class ClxDerived!" << endl;
    };
};
int   main() {
    ClxBase *p =  new ClxDerived;
    p->DoSomething();
    delete p;
    return 0;
}

运行结果:

Do something in class ClxDerived!
Output from the destructor of class ClxDerived!
Output from the destructor of class ClxBase!

 

这段代码中基类的析构函数被定义为虚函数,在main函数中用基类的指针去操作继承类的成员,释放指针P的过程是:只是释放了继承类的资源,再调用基类的析构函数.调用dosomething()函数执行的也是继承类定义的函数.

如果不需要基类对派生类及对象进行操作,则不能定义虚函数,因为这样会增加内存开销.当类里面有定义虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间.所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数.

转自 http://blog.sina.com.cn/s/blog_7c773cc50100y9hz.html

 

其实这个问题最终将回答一个问题:

如果Base * pbase = new Derived;那么如果delete pbase的话,怎样避免内存泄露?

对比1:父类的普通成员函数和虚函数均是非虚函数

来看看这时候会发生什么,具体代码和运行结果如下:

#include <iostream> 
using namespace std; 
 
//情景1:普通成员函数和析构函数,都不是虚函数 
class Base{ 
public: 
    Base(){} 
    //父类的析构函数,不是虚函数 
    ~Base(){ 
        cout<<"Base destructor"<<endl; 
    } 
    //普通成员 
    void dosomething(){ 
        cout<<"do something in Base"<<endl; 
    } 
}; 
//派生类 
class Derived : public Base{ 
public: 
    Derived(){} 
    ~Derived(){ 
        cout<<"Derived destructor"<<endl; 
    } 
    void dosomething(){ 
        cout<<"do something in Derived"<<endl; 
    } 
}; 
 
int main(){ 
    //这个时候,虽然父类指针实际指向子类,可是没有虚函数表,所以不能调用实际的类型,因此输出的只能是父类指针自身能看到的内容 
    Base *base = new Derived; 
    base->dosomething();//输出的是父类的函数 
    delete base;//调用的是父类的析构函数 
    system("pause"); 
    return 0; 
} 

技术分享

其实涉及到虚函数,必然要想到虚函数表,虚函数表就是对类中的所有虚函数维持的数据结构,若是父类指针调用子类对象,这时候会查找虚函数表到实际的类型。可是如果没有虚函数,父类指针只能“看到”自身的成员,这时候自然只能调用本身的方法(普通方法和析构方法);

因为没有虚构函数,那么pbase指针只能调用子类对象中的父类对象部分的方法,因此只能调用父类的析构函数。

对比2:父类析构函数是虚函数(将调用哪个析构函数?)

将代码的父类的虚构函数加上virtual关键字,其他完全相同,得到如下代码和运行结果:

#include <iostream> 
using namespace std; 
 
//情景1:普通成员函数和析构函数,都不是虚函数 
class Base{ 
public: 
    Base(){} 
    //父类的析构函数,是虚函数,只做了这里的更改 
    virtual ~Base(){ 
        cout<<"Base destructor"<<endl; 
    } 
    //普通成员 
    void dosomething(){ 
        cout<<"do something in Base"<<endl; 
    } 
}; 
//派生类 
class Derived : public Base{ 
public: 
    Derived(){} 
    ~Derived(){ 
        cout<<"Derived destructor"<<endl; 
    } 
    void dosomething(){ 
        cout<<"do something in Derived"<<endl; 
    } 
}; 
 
int main(){ 
    //这个时候,虽然父类指针实际指向子类,可是没有虚函数表,所以不能调用实际的类型,因此输出的只能是父类指针自身能看到的内容 
    Base *base = new Derived; 
    base->dosomething();//输出的是父类的函数 
    delete base;//调用的是父类的析构函数 
    system("pause"); 
    return 0; 
} 

技术分享

由此可以看出,这个时候,就真正实现了将实际类型对象进行释放的。同时可以得到,如果父类析构是虚函数,子类调用析构函数的话,会先调用子类的析构函数,之后会调用父类的析构函数

其实这里父类的析构函数加上了virtual,并不是说pbase释放的时候,同时调用了子类的析构函数和父类的析构函数,它实际上指向的是子类的虚函数表,那么就是说父类指针最终只调用了子类的析构函数,由C++类本身特性,当子类析构函数调用的时候,会自动调用父类的析构函数,完成了释放。

相关小结

对于Base *pbase = new Derived;

  1. 如果父类函数不是析构函数,那么pbase只能“看见”父类本身的函数,这是因为没有虚函数表让它可以找到本身
  2. 如果父类析构函数是虚函数,如果delete pbase,将会先调用子类函数的析构函数,然后子类析构函数自动调用父类的析构函数,真正实现了资源释放,防止了内存泄露
  3. 构造派生类的时候,会先构造基类部分,然后构造子类部分;撤销派生类对象的时候,会先撤销派生类部分,然后撤销基类部分

转载自http://www.51projob.com/a/bishimianshi/2012/0414/195.html

虚函数

标签:

原文地址:http://www.cnblogs.com/daijkstra/p/4844053.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!