`

虚析构函数(√)、纯虚析构函数(√)、虚构造函数(X)

    博客分类:
  • java
 
阅读更多

一. 虚析构函数

我们知道,为了能够正确的调用对象的析构函数,一般要求具有层次结构的顶级类定义其析构函数为虚函数。因为在delete一个抽象类指针时候,必须要通过虚函数找到真正的析构函数。

如:

class Base
{
public:
    Base(){}
   virtual ~Base(){}
};

class Derived: public Base
{
public:
    Derived(){};
   ~Derived(){};
}

void foo()
{
    Base *pb;
    pb = new Derived;
    delete pb;
} 这是正确的用法,会发生动态绑定,它会先调用Derived的析构函数,然后是Base的析构函数

如果析构函数不加virtual,delete pb只会执行Base的析构函数,而不是真正的Derived析构函数。
因为不是virtual函数,所以调用的函数依赖于指向静态类型,即Base

二. 纯虚析构函数
现在的问题是,我们想把Base做出抽象类,不能直接构造对象,需要在其中定义一个纯虚函数。如果其中没有其他合适的函数,可以把析构函数定义为纯虚的,即将前面的CObject定义改成:
class Base
{
public:
    Base(){}
   virtual ~Base()= 0
};可是,这段代码不能通过编译,通常是link错误,不能找到~Base()的引用(gcc的错误报告)。这是因为,析构函数、构造函数和其他内部函数不一样,在调用时,编译器需要产生一个调用链。也就是,Derived的析构函数里面隐含调用了Base的析构函数。而刚才的代码中,缺少~Base()的函数体,当然会出现错误。

这里面有一个误区,有人认为,virtual f()=0这种纯虚函数语法就是没有定义体的语义。
其实,这是不对的。这种语法只是表明这个函数是一个纯虚函数,因此这个类变成了抽象类,不能产生对象。我们完全可以为纯虚函数指定函数体 (http://www.research.att.com/~bs/bs_faq2.html#pure-virtual)。通常的纯虚函数不需要函数体,是因为我们一般不会调用抽象类的这个函数,只会调用派生类的对应函数。这样,我们就有了一个纯虚析构函数的函数体,上面的代码需要改成:
class Base
{
public:
    Base()
    {
    }
   virtual ~Base() = 0; //pure virtual
};

Base::~Base()//function body
{
}
从语法角度来说,不可以将上面的析构函数直接写入类声明中(内联函数的写法)。这或许是一个不正交化的地方。但是这样做的确显得有点累赘

这个问题看起来有些学术化,因为一般我们完全可以在Base中找到一个更加适合的函数,通过将其定义为没有实现体的纯虚函数,而将整个类定义为抽象类。但这种技术也有一些应用,如这个例子:
class Base  //abstract class
{
public:
   virtual ~Base(){};//virtual, not pure
   virtual void Hiberarchy() const = 0;//pure virtual
};

void Base::Hiberarchy() const //pure virtual also can have function body
{
    std::cout <<"Base::Hiberarchy";
}

class Derived : public Base
{
public:
    Derived(){}
   virtual void Hiberarchy() const
    {
        CB::Hiberarchy();
        std::cout <<"Derived::Hiberarchy";
    }
   virtual void foo(){}
};


int main(){
    Base* pb=new Derived();
    pb->Hiberarchy();
    pb->Base::Hiberarchy();
   return 0;
}


在这个例子中,我们试图打印出类的继承关系。在根基类中定义了虚函数Hiberarchy,然后在每个派生类中都重载此函数。我们再一次看到,由于想把Base做成个抽象类,而这个类中没有其他合适的方法成员可以定义为纯虚的,我们还是只好将Hiberarchy定义为纯虚的。(当然,完全可以定义~Base函数,这就和上面的讨论一样了。^_^)

另外,可以看到,在main中有两种调用方法,第一种是普通的方式,进行动态链接,执行虚函数,得到结果"Derived::Hiberarchy";第二种是指定类的方式,就不再执行虚函数的动态链接过程了,结果是"Base::Hiberarchy"。

通过上面的分析可以看出,定义纯虚函数的真正目的是为了定义抽象类,而并不是函数本身。与之对比,在java中,定义抽象类的语法是 abstract class,也就是在类的一级作指定(当然虚函数还是也要加上abstract关键字)。是不是这种方式更好一些呢?在Stroustrup的《C++语言的设计与演化》中我找到这样一段话:

“我选择的是将个别的函数描述为纯虚的方式,没有采用将完整的类声明定义为抽象的形式,这是因为纯虚函数的概念更加灵活一些。我很看重能够分阶段定义类的能力;也就是说,我发现预先定义一些纯虚函数,并把另外一些留给进一步的派生类去定义也是很有用的”。

我还没有完全理解后一句话,我想从另外一个角度来阐述这个概念。那就是,在一个多层复杂类结构中,中间层次的类应该具体化一些抽象函数,但很可能并不是所有的。中间类没必要知道是否具体化了所有的虚函数,以及其祖先已经具体化了哪些函数,只要关注自己的职责就可以了。也就是说,中间类没必要知道自己是否是一个真正的抽象类,设计者也就不用考虑是否需要在这个中间类的类级别上加上类似abstract的说明了。

当然,一个语言的设计有多种因素,好坏都是各个方面的。这只是一个解释而已。

最后,总结一下关于虚函数的一些常见问题:

1) 虚函数是动态绑定的,也就是说,使用虚函数的指针和引用能够正确找到实际类的对应函数,而不是执行定义类的函数。这是虚函数的基本功能,就不再解释了。

2) 构造函数不能是虚函数。而且,在构造函数中调用虚函数,实际执行的是父类的对应函数,因为自己还没有构造好, 多态是被disable的。

3) 析构函数可以是虚函数,而且,在一个复杂类结构中,这往往是必须的。

4) 将一个函数定义为纯虚函数,实际上是将这个类定义为抽象类,不能实例化对象。

5) 纯虚函数通常没有定义体,但也完全可以拥有

6) 析构函数可以是纯虚的,但纯虚析构函数必须有定义体,因为析构函数的调用是在子类中隐含的。

7) 非纯的虚函数必须有定义体,不然是一个错误。

8) 派生类的override虚函数定义必须和父类完全一致。除了一个特例,如果父类中返回值是一个指针或引用,子类override时可以返回这个指针(或引用)的派生。例如,在上面的例子中,在Base中定义了 virtual Base* clone(); 在Derived中可以定义为 virtual Derived* clone()。可以看到,这种放松对于Clone模式是非常有用的。

此贴是转载的,并不是原创的文章,不过对于学习有用,就用了拿来主义,感觉作者对于

C++虚函数的总结还是比较的到位的,逻辑也很缜密,故而拿来借鉴。

分享到:
评论

相关推荐

    C_虚构造函数和虚析构函数.pdf

    C_虚构造函数和虚析构函数.pdf

    C++虚析构函数的使用分析

    首先解释一下虚构函数和指针之间是如何交互的,以及虚析构函数的具体含义。例如以下代码,其中SomeClass是含有非virtual析构函数的一个类:SomeClass *p= new SomeClass;. . . . . .delect p;为p调用delect时,会...

    c++多态性与虚函数习题答案 (2).docx

    C++的多态性是面向对象编程中的一个重要概念,它允许...抽象类和纯虚函数是设计接口和定义行为规范的工具,而虚析构函数则是确保正确释放对象资源的关键。理解并熟练运用这些概念对于编写高效、可靠的C++代码至关重要。

    析构函数与php的垃圾回收机制详解

    析构函数:当某个对象成为垃圾或者当对象被显式销毁时执行。 GC(Garbage Collector) ... ... ... __destruct() 析构函数 ...析构函数是由系统自动调用的,不要在程序中调用一个对象的虚构函数。 析构函

    C++复习题1.docx

    虚析构函数用于正确销毁派生类对象,即使通过基类指针。 16. Windows应用程序结构:MFC中的典型结构包括单文档界面(SDI)、多文档界面(MDI)和对话框(Dialog)应用程序。 17. 消息与消息映射:消息是Windows...

    C++深刻理解继承与虚构

    - 如果一个类作为基类存在,并且可能有派生类对象通过基类指针删除,那么应该将析构函数声明为虚函数,以确保正确的清理顺序。例如: ```cpp class Base { public: virtual ~Base() {} // 虚析构函数 }; ``` ...

    习题8及其解答(第二版).doc

    4. 构造函数与析构函数: 构造函数和析构函数不能被声明为虚函数,因为它们在对象生命周期的开始和结束阶段执行,此时多态性还未生效。然而,派生类的构造函数可以调用基类的虚构造函数,这样在构造派生类对象时,...

    C++ 构造函数中使用new时注意事项

    在有多个构造函数的情况下,必须以相同的方式使用new,要不用new,要不用new[],因为只存在一个析构函数,所有的构造函数都必须与虚构函数相兼容。 PS. 当然在构造函数中使用new初始化指针的时候,可以把指针初始化...

    C/C++语言 面试考题

    1. **虚构造函数/析构函数**:虚构造函数允许子类对象通过基类指针或引用进行构造,而虚析构函数确保在删除对象时正确调用派生类的析构函数,防止资源泄露。 2. **纯虚函数**:纯虚函数(virtual void func()=0)...

    Bjarne Stroustrup的FAQ:C++的风格与技巧

    如果类层次结构中的基类的析构函数不是虚的,那么在通过指向基类的指针删除派生类对象时,只会调用基类的析构函数,从而导致资源泄露。 ##### (7) 为什么不能有虚拟构造函数? 构造函数不能是虚函数,因为构造函数...

    C++编码规范.

    **原则3.21 关于虚构造函数** - 构造函数不能是虚的,但在某些情况下可使用工厂模式来模拟。 - 示例:使用工厂模式创建对象。 --- #### 第4章 类的设计和声明 **原则4.1 类应是描述一组对象的集合** - 类应描述一...

    Caso3:Caso 3,《瓜迪奥·梅戈·蒂姆波·阿尔戈里特莫斯》

    3. **构造函数与析构函数**:构造函数是在创建对象时自动调用的特殊成员函数,用于初始化对象的成员变量;析构函数则在对象生命周期结束时被调用,用于清理资源。 4. **封装**:封装是面向对象编程的基本原则之一,...

    hyperborea_class_creation

    如果Hyperborea类需要在对象不再使用时释放资源,可能需要定义析构函数。然而,在Python中,由于其垃圾回收机制,通常不需要显式地定义析构函数。 9. **文件“hyperborea_class_creation-main”**: 这个文件可能...

    Ex3-Godzilla vs Kong_king_C++_

    4. **构造与析构函数**:对象生命周期的管理。 5. **成员函数和数据成员**:如何通过成员函数操作对象的状态。 6. **输入输出**:使用`std::cin`和`std::cout`进行标准输入输出,可能还会涉及格式化输出。 7. **异常...

    戏说面向对象 c#版

    构造函数用于初始化对象,析构函数则用于清理对象资源。访问修饰符如`public`、`private`、`protected`和`internal`控制类成员的可见性。属性和索引器提供了一种安全的方式来访问和修改对象的内部状态。事件和委托是...

    lab05_taffe_carter

    4. **构造函数与析构函数**:学习如何在类中定义构造函数以初始化对象,以及析构函数来清理资源。 5. **封装、继承和多态**:封装是隐藏实现细节,继承允许创建新的类基于现有类,多态则是通过虚函数实现接口的多种...

Global site tag (gtag.js) - Google Analytics