zoukankan      html  css  js  c++  java
  • 为什么构造函数不能声明为虚函数,析构函数可以

    构造函数不能声明为虚函数,析构函数可以声明为虚函数,而且有时是必须声明为虚函数。
    不建议在构造函数和析构函数里面调用虚函数。

    构造函数不能声明为虚函数的原因是:
    1 构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型的。而在构造一个对象时,由于对象还未构造成功。编译器无法知道对象 的实际类型,是该类本身,还是该类的一个派生类,或是更深层次的派生类。无法确定。。。

    2 虚函数的执行依赖于虚函数表。而虚函数表在构造函数中进行初始化工作,即初始化vptr,让他指向正确的虚函数表。而在构造对象期间,虚函数表还没有被初 始化,将无法进行。

    虚函数的意思就是开启动态绑定,程序会根据对象的动态类型来选择要调用的方法。然而在构造函数运行的时候,这个对象的动态类型还不完整,没有办法确定它到底是什么类型,故构造函数不能动态绑定。(动态绑定是根据对象的动态类型而不是函数名,在调用构造函数之前,这个对象根本就不存在,它怎么动态绑定?)
    编译器在调用基类的构造函数的时候并不知道你要构造的是一个基类的对象还是一个派生类的对象。

    析构函数设为虚函数的作用:
    解释:在类的继承中,如果有基类指针指向派生类,那么用基类指针delete时,如果不定义成虚函数,派生类中派生的那部分无法析构。
    例:
    #include "stdafx.h"
    #include "stdio.h"
    class A
    {
    public:
    A();
    virtual~A();
    };
    A::A()
    {
    }

    A::~A()
    {
    printf("Delete class APn");
    }
    class B : public A
    {
    public:
    B();
    ~B();
    };

    B::B()
    { }

    B::~B()
    {
    printf("Delete class BPn");
    }
    int main(int argc, char* argv[])
    {
    A *b=new B;
    delete b;
    return 0;
    }


    输出结果为:Delete class B
    Delete class A

    如果把A的virtual去掉:那就变成了Delete class A也就是说不会删除派生类里的剩余部分内容,也即不调用派生类的虚函数

    因此在类的继承体系中,基类的析构函数不声明为虚函数容易造成内存泄漏。所以如果你设计一定类可能是基类的话,必须要声明其为虚函数。正如Symbian中的CBase一样。

    Note:
    1. 如果我们定义了一个构造函数,编译器就不会再为我们生成默认构造函数了。
    2. 编译器生成的析构函数是非虚的,除非是一个子类,其父类有个虚析构,此时的函数虚特性来自父类。
    3. 有虚函数的类,几乎可以确定要有个虚析构函数。
    4. 如果一个类不可能是基类就不要申明析构函数为虚函数,虚函数是要耗费空间的。
    5. 析构函数的异常退出会导致析构不完全,从而有内存泄露。最好是提供一个管理类,在管理类中提供一个方法来析构,调用者再根据这个方法的结果决定下一步的操作。
    6. 在构造函数不要调用虚函数。在基类构造的时候,虚函数是非虚,不会走到派生类中,既是采用的静态绑定。显然的是:当我们构造一个子类的对象时,先调用基类的构造函数,构造子类中基类部分,子类还没有构造,还没有初始化,如果在基类的构造中调用虚函数,如果可以的话就是调用一个还没有被初始化的对象,那是很危险的,所以C++中是不可以在构造父类对象部分的时候调用子类的虚函数实现。但是不是说你不可以那么写程序,你这么写,编译器也不会报错。只是你如果这么写的话编译器不会给你调用子类的实现,而是还是调用基类的实现。
    7.
    在析构函数中也不要调用虚函数。在析构的时候会首先调用子类的析构函数,析构掉对象中的子类部分,然后在调用基类的析构函数析构基类部分,如果在基类的析构函数里面调用虚函数,会导致其调用已经析构了的子类对象里面的函数,这是非常危险的。

    8. 记得在写派生类的拷贝函数时,调用基类的拷贝函数拷贝基类的部分,不能忘记了。

     1 1.第一段代码
     2 #include<iostream>
     3 using namespace std;
     4 class ClxBase{
     5 public:
     6     ClxBase() {};
     7     ~ClxBase() {cout << "Output from the destructor of class ClxBase!" << endl;};
     8     void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
     9 };
    10 class ClxDerived : public ClxBase{
    11 public:
    12     ClxDerived() {};
    13     ~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };
    14     void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };
    15 };
    16   int   main(){  
    17   ClxDerived *p =  new ClxDerived;
    18   p->DoSomething();
    19   delete p;
    20   return 0;
    21   }
    22 运行结果:
    23 Do something in class ClxDerived!            
    24 Output from the destructor of class ClxDerived!
    25 Output from the destructor of class ClxBase!  
    26     这段代码中基类的析构函数不是虚函数,在main函数中用继承类的指针去操作继承类的成员,释放指针P的过程是:先释放继承类的资源,再释放基类资源. 
     1 2.第二段代码
     2 #include<iostream>
     3 using namespace std;
     4 class ClxBase{
     5 public:
     6     ClxBase() {};
     7     ~ClxBase() {cout << "Output from the destructor of class ClxBase!" << endl;};
     8     void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
     9 };
    10 class ClxDerived : public ClxBase{
    11 public:
    12     ClxDerived() {};
    13     ~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };
    14     void DoSomething() { cout << "Do something in class ClxDerived!" << endl; }
    15 };
    16   int   main(){  
    17   ClxBase *p =  new ClxDerived;
    18   p->DoSomething();
    19   delete p;
    20   return 0;
    21   } 
    22 输出结果:
    23 Do something in class ClxBase!
    24 Output from the destructor of class ClxBase!
    25     这段代码中基类的析构函数同样不是虚函数,不同的是在main函数中用基类的指针去操作继承类的成员,释放指针P的过程是:只是释放了基类的资源,而没有调用继承类的析构函数.调用dosomething()函数执行的也是基类定义的函数.
    26     一般情况下,这样的删除只能够删除基类对象,而不能删除子类对象,形成了删除一半形象,造成内存泄漏.
    27     在公有继承中,基类对派生类及其对象的操作,只能影响到那些从基类继承下来的成员.如果想要用基类对非继承成员进行操作,则要把基类的这个函数定义为虚函数.
    28     析构函数自然也应该如此:如果它想析构子类中的重新定义或新的成员及对象,当然也应该声明为虚的. 
    29  
    30 3.第三段代码:
    31 #include<iostream>
    32 using namespace std;
    33 class ClxBase{
    34 public:
    35     ClxBase() {};
    36     virtual ~ClxBase() {cout << "Output from the destructor of class ClxBase!" << endl;};
    37     virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
    38 };
    39 class ClxDerived : public ClxBase{
    40 public:
    41     ClxDerived() {};
    42     ~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };
    43     void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };
    44 };
    45   int   main(){  
    46   ClxBase *p =  new ClxDerived;
    47   p->DoSomething();
    48   delete p;
    49   return 0;
    50   }  
    51 运行结果:
    52 Do something in class ClxDerived!
    53 Output from the destructor of class ClxDerived!
    54 Output from the destructor of class ClxBase!
    55     这段代码中基类的析构函数被定义为虚函数,在main函数中用基类的指针去操作继承类的成员,释放指针P的过程是:只是释放了继承类的资源,再调用基类的析构函数.调用dosomething()函数执行的也是继承类定义的函数. 
    56  
    57     如果不需要基类对派生类及对象进行操作,则不能定义虚函数,因为这样会增加内存开销.当类里面有定义虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间.所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数.
  • 相关阅读:
    spring
    C++容器常用方法简单总结
    【转】shell中各种括号的作用详解()、(())、[]、[[]]、{}
    c++创建对象时一些小细节
    ros建模与仿真(urdf介绍)
    常用vi命令
    Linux零零碎碎的小知识
    Linux目录都是些什么
    关于c/c++指针,指针的指针
    关于c/c++中的二维数组与指针
  • 原文地址:https://www.cnblogs.com/lpxblog/p/5890933.html
Copyright © 2011-2022 走看看