C++箴言:多态基类中将析构函数声明为虚拟

合集下载

C++基抽象类的构造析构(纯)虚函数

C++基抽象类的构造析构(纯)虚函数

C++基抽象类的构造析构(纯)虚函数⼀、析构函数可定义为⼀、析构函数可定义为纯虚函数纯虚函数,但也必须给出函数定义,但也必须给出函数定义 Effective C++ 条歀07: 为多态基类声明virtual 析构函数(Declare destructors virtual in polymorphic base classes ) 在某些类⾥声明纯虚析构函数很⽅便。

纯虚函数将产⽣抽象类——不能实例化的类(即不能创建此类型的对象)。

有些时候,你想使⼀个类成为抽象类,但刚好⼜没有任何纯虚函数。

怎么办?因为抽象类是准备被⽤做基类的,基类必须要有⼀个虚析构函数,纯虚函数会产⽣抽象类,所以⽅法很简单:在想要成为抽象类的类⾥声明⼀个纯虚析构函数。

1 //这⾥是⼀个例⼦:2 class awov {3 public :4 virtual ~awov() = 0; // 声明⼀个纯虚析构函数5 }; 这个类有⼀个纯虚函数,所以它是抽象的,⽽且它有⼀个虚析构函数,所以不会产⽣析构函数问题。

但这⾥还有⼀件事:必须提供纯虚析构函数的定义: awov::~awov() { ... } // 纯虚析构函数的定义 这个定义是必需的,因为虚析构函数⼯作的⽅式是:最底层的派⽣类的析构函数最先被调⽤,然后各个基类的析构函数被调⽤。

这就是说,即使是抽象类,编译器也要产⽣对~awov 的调⽤,所以要保证为它提供函数体。

如果不这么做,链接器就会检测出来,最后还是得回去把它添上。

⼆、? 关于C++为什么不⽀持虚拟构造函数,Bjarne 很早以前就在C++Style and Technique FAQ ⾥⾯做过回答 Avirtual call is a mechanism to get work done given partialinformation. In particular, "virtual" allows us to call afunction knowing only an interfaces and not the exact type of theobject. To create an object you need complete information.Inparticular, you need to know the exact type of what you want tocreate. Consequently, a "call to a constructor" cannot bevirtual. 含义⼤概是这样的:虚函数调⽤是在部分信息下完成⼯作的机制,允许我们只知道接⼝⽽不知道对象的确切类型。

C++考试试题重点

C++考试试题重点

一、概念题1.类和对象有什么区别和联系?类是一种复杂的数据类型,它是将不同类型的数据和与这些数据相关的操作封装在一起的集合体。

类是对某一类对象的抽象,而对象是某一种类的实例。

2.什么是类的实现?将类所有未编写函数体的成员函数在类体外全部编写出来。

3.this指针的概念是什么?类中所有的成员函数(静态成员函数除外)都隐含了第一个参数,这个隐含的第一个参数就是this指针,在成员函数的实现代码中,所有涉及对类的数据成员的操作都隐含为对this 指针所指对象的操作。

4.为什么要引入构造函数和析构函数?构造函数的作用是为类对象的数据成员赋初值,构造函数在定义类对象时由系统自动调用;在一个对象死亡或者说退出生存期时,系统会自动调用析构函数,因此可以在析构函数定义中,设置语句释放该对象所占用的一些资源。

5.什么时候需要自定义拷贝构造函数?若程序员没有定义拷贝构造函数,则编译器自动生成一个缺省的拷贝构造函数,它可能会产生什么问题?当有用一个已经存在对象创建一个同类型的新对象的需求时。

当对象含有指针数据成员,并用它初始化同类型的另一个对象时,默认的拷贝构造函数只能将该对象的数据成员复制给另一个对象,而不能将该对象中指针所指向的内存单元也复制过去。

这样,就可能出现同一内存单元释放两次,导致程序运行出错。

6.什么是堆对象?创建和回收堆对象的方法是什么?堆区用来存放在程序运行期间,根据需要随时建立的变量(对象),建立在堆区的对象称为堆对象,当堆对象不再使用时,应予以删除,回收所占用的动态内存。

创建和回收堆对象的方法是使用new和delete运算符。

7.为什么需要静态数据成员?静态数据成员的定义和初始化方法是什么?定义静态数据成员是为了同一个类的不同对象之间共享公共数据成员;用关键字static 可以把数据成员定义成静态数据成员;在定义的类被使用前,要对其中的静态数据成员进行初始化,初始化时不必添加关键字static。

CPP复习题答案

CPP复习题答案

13. 派生类的对象对它的基类成员中 ( A ) 是可以采用对象·或者对象指针->的方 B. 公有继承的私有成员 D. 私有继承的公有成员 C ) 。
14. 关于纯虚函数和抽象类的描述中,错误的是( B. 抽象类是指具有纯虚函数的类。
A. 纯虚函数是一种特殊的虚函数,它没有具体的实现。 C. 一个基类中说明有纯虚函数,该基类的派生类一定不再是抽象类。 D. 抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。 15.下列说法错误的是( C )。 A.若语言只支持类而不支持多态,则不能称为面向对象的。 B.在运行时根据其类型确定调用哪个函数的能力叫多态性。 C.静态多态性也是在运行时根据其类型确定调用哪个函数。 D.C++中的静态多态性是通过函数重载进行实现的。 16. ( D )不是构造函数的特征 A. 构造函数的函数名与类名相同。 B. 构造函数可以重载。 C. 构造函数可以设置缺省参数。 D. 构造函数必须指定类型说明。 17.下列标识符中, A 不是 C++的关键字; A. cin B. private C. this D. operator 18.下列标识符中, A. cout A. cout 18.下列标识符中, A A 不是 C++的关键字; C. this C. this D. template D. sizeof 不是 C++的关键字; 不是 C++的关键字; C. this D. sizeof B. virtual B. public
A. 缩短程序代码,少占用内存空间 B. 既可以保证程序的可读性,又能提高程序的运行效率 C. 占用内存空间少,执行速度快 D. 使程序的结构比较清晰 3. 重载函数是( A ) A. 以函数参数来区分,而不用函数的返回值来区分不同的函数 B. 以函数的返回值来区分,而不用函数参数来区分不同的函数 C. 参数表完全相同而返回值类型不同的两个或多个同名函数 D. 参数表和返回值类型都必须是不同的两个或多个同名函数 4. 在 C++中,数据封装要解决的问题是( A. 数据的规范化 C. 避免数据丢失 5. 下列特性中,( B A.继承 之间的关系是( A. 组合关系 B.内联函数 C )。 B. 间接关系 C. 继承关系 D. 直接关系 D )。 B. 便于数据转换 D. 防止不同模块之间数据的非法访问 )不是面向对象的程序设计的特征。 C.多态性 D.封装

C++复习题及参考答案

C++复习题及参考答案

31关于友元类的描述中,错误的是(C )A.友元类中的成员函数都是友元函数B. 友元类被说明在一个类中,它与访问权限无关C. 友元类是被定义在某个类中的嵌套类D. 如果Y是类X的友元,则类X不一定是类Y的友元32友元在访问类的对象的成员时(D)。

A)直接使用类的成员名B)使用this指针指向成员名C)使用“类名::成员名”的形式 D)使用“对象名.成员名”的形式33如果类A被说明成类B的友元,则(D )。

A. 类A的成员即类B的成员B. 类B的成员即类A的成员C. 类A的成员函数不得访问类B的成员D. 类B不一定是类A的友元34在公有继承的情况下,基类成员在派生类中的访问权限( B )。

A、受限制B、保持不变C、受保护D、不受保护35类A是类B的友元,类B是类C的友元,则(D)。

A. 类B是类A的友元B. 类C是类A的友元C. 类A是类C的友元D. 以上都不对36.下列运算符中,(A)不能在C++中重载A.?: B.[] C.new D.&&37.下列运算符不能用友元函数重载的是(C) //这个题目之前出错了,修改了。

A.+ B.- C..* D.<<38.关于运算符重载的下列描述中正确的是(B)A.可改变优先级B.需保持原有的结合性C.可改变操作数的个数 D.可改变语法结构39.为了满足运算符+的可交换性,必须将其重载为(D)A.成员函数 B.静态成员函数 C.虚函数 D.友元函数40 下列对派生类的描述中,( C)是错误的。

A. 一个派生类可以作为另一个派生类的基类B. 派生类至少应有一个基类C. 基类中成员的访问权限被派生类继承后都不改变D. 派生类的成员除了自己定义的成员外,还包含了它的基类成员二.判断正误(1) 析构函数是一个函数体为空的成员函数。

(×)(2)虚函数是为实现某种功能而假设的函数,派生类的虚函数定义影响其基类,而基类的虚函数定义不影响其派生类。

c++ virtual修饰析构函数

c++ virtual修饰析构函数

C++中的virtual修饰析构函数1. 概述在C++中,虚析构函数是一种特殊的析构函数,可以让派生类对象在被删除时,能够适当地调用其基类析构函数。

这种机制可以实现多态性,对于构建一个多态的对象继承体系是非常有用的。

2. 什么是虚析构函数虚析构函数是在基类中将析构函数声明为虚函数。

在C++中,虚析构函数要求在基类中用virtual关键字声明,在派生类中重写。

这样可以确保当基类的指针指向派生类的对象时,能够正确地调用派生类的析构函数。

3. 虚析构函数的作用虚析构函数的作用是解决当基类指针指向派生类对象时,只调用基类析构函数的问题。

如果不使用虚析构函数,当通过基类指针来删除继承类的对象时,只会调用基类的析构函数,而不会调用派生类的析构函数,导致可能会出现资源泄漏或者未能正确释放资源的问题。

4. 虚析构函数的声明方式虚析构函数只需在基类中进行声明即可,在派生类中无需再次声明为虚函数。

虚析构函数的声明方式如下所示:```cppclass Base {public:virtual ~Base() {// 析构函数的实现}};```5. 虚析构函数的示例下面是虚析构函数的一个简单示例:```cppclass Base {public:Base() {std::cout << "Base constructor" << std::endl; }virtual ~Base() {std::cout << "Base destructor" << std::endl; }};class Derived : public Base {public:Derived() {std::cout << "Derived constructor" << std::endl;}~Derived() {std::cout << "Derived destructor" << std::endl;}};int m本人n() {Base* ptr = new Derived();delete ptr;return 0;}```在上面的示例中,基类Base中声明了虚析构函数,而派生类Derived 中覆盖了基类的析构函数。

virtualfree函数的详细用法

virtualfree函数的详细用法

虚函数是C++中的一个非常重要的概念,它允许我们在派生类中重新定义基类中的函数,从而实现多态性。

在本文中,我们将深入探讨virtual关键字的作用,以及virtual函数和纯虚函数的使用方法。

在C++中,virtual关键字用于声明一个虚函数。

这意味着当派生类对象调用该函数时,将会调用其在派生类中的定义,而不是基类中的定义。

这种行为使得我们能够在派生类中定制化地实现函数的逻辑,从而实现不同对象的不同行为。

对于virtual函数,我们需要注意以下几点:1. 在基类中声明函数时,使用virtual关键字进行声明。

2. 派生类中可以选择性地使用virtual关键字进行重声明,但通常最好也使用virtual,以便明确表明这是一个虚函数。

3. 当使用派生类对象调用虚函数时,将会根据对象的实际类型调用适当的函数实现。

4. 虚函数的实现通过虚函数表(vtable)来实现,这是一张函数指针表,用于存储各个虚函数的位置区域。

除了普通的虚函数外,C++还提供了纯虚函数的概念。

纯虚函数是在基类中声明的虚函数,它没有函数体,只有声明。

这意味着基类不能直接实例化,只能用作其他类的基类。

纯虚函数通常用于定义一个接口,而具体的实现则留给派生类。

接下来,让我们以一个简单的例子来说明虚函数和纯虚函数的用法。

假设我们有一个基类Shape,它包含一个纯虚函数calcArea用于计算面积。

有两个派生类Circle和Rectangle,它们分别实现了calcArea 函数来计算圆形和矩形的面积。

在这个例子中,我们可以看到基类Shape定义了一个纯虚函数calcArea,它没有函数体。

而派生类Circle和Rectangle分别实现了这个函数来计算不同形状的面积。

当我们使用Shape指针指向Circle或Rectangle对象时,调用calcArea函数将会根据对象的实际类型来调用适当的实现。

除了虚函数和纯虚函数外,C++中还有虚析构函数的概念。

C++之virtual析构函数

C++之virtual析构函数

C++之virtual析构函数1.析构函数class A{public:A();~A(); //析构函数}2.析构函数需要声明为virtual条件:当定义的类中含有除去析构函数外的其他函数为virtual函数时,这时需要将此类的析构函数定义为virtual函数;3.virtual析构函数好处:当⽗类指针指向⼦类对象时,执⾏释放操作,⼦类对象也会被释放掉class Base{public:Base();virtual ~Base();//...}class Derived: public Base{public:Derived();~Derived();//...}当有如下调⽤时:Base *base =new Derived(); //⽗类的⼀个指针实际向⼦类对象delete base ;将Base析构函数声明为virtual函数时,执⾏delete base ;语句就会删除derived的对象;4.析构函数的virtual使⽤不正确时例如class Point{public :Point(int x,int y);~Point();private:int x,y;}需要实现出virtual函数时,对象就必须携带某些信息来决定在运⾏期调⽤哪⼀个virtaul函数,通常是由vptr(virtual table point)指针决定的,它指向⼀个由函数指针构成的数组,称为vtbl(virtual table);每⼀个class都有⼀个对应的vtbl。

当对象调⽤某⼀virtual函数时,实际被调⽤的函数取决于该对象的vptr所指的那个vtbl。

含有virtual的函数其对象的体积会增加,因为它多了⼀个vptr指针,所以C++的Point对象就不能和其他语⾔有着⼀样的声明结构了,因为也不再具有可移植性。

综上所述,所以当定义析构函数为虚函数时需要知道当前类是否还有⼦类,如果没有⼦类,则可以将其析构函数不定义为虚函数,否则则定义为虚函数。

C语言试题

C语言试题

1.下面各项不属于派生新类范畴的是(C )A.吸收基类的成员B.改造基类的成员C.删除基类的成员D.添加新成员2.在派生新类的过程中,( D )A.基类的所有成员都被继承B.只有基类的构造函数不被继承C.只有基类的析构函数不被继承D.基类的构造函数和析构函数都不被继承3.下面不属于类的继承方式的是( C )A.publicB.privateC.operatorD.protected4.作用域分辨符是指( B )A.?:B.::C.->D.&&5.专用多态是指( A )A.重载多态和强制多态B.强制多态和包含多态C.包含多态和参数多态D.参数多态和重载多态6.通用多态是指( C )A.重载多态和强制多态B.强制多态和包含多态C.包含多态和参数多态D.参数多态和重载多态7.下面各项中属于不可重载的一组运算符是( C )A.+、—、*、/B.[ ]、()C.::、.、?:、sizeof、.*D.++、——8.关于类的构造函数,下面说法不正确的是( C )A.构造函数的作用是在对象被创建时将对象初始化为一个特定的状态B.构造函数的函数名与类名相同C.构造函数可以声明为虚函数D.构造函数在对象被创建时被系统自动调用9.在C++中,数组类型属于( B )A.基本数据类型B.自定义数据类型D.结构体类型10.若有语句:float array[3][5][7];则数组array的元素个数为( D )A.3B.5C.7D.10511.关于虚基类,下面说法正确的是( D )A.带有虚函数的类称为虚基类B.带有纯虚函数的类称为虚基类C.虚基类不能实例化D.虚基类可以用来解决二义性问题12.关于析构函数,下面说法不正确的是( B )A.析构函数用来完成对象被删除前的一些清理工作B.析构函数可以声明为重载函数C.析构函数可以声明为虚函数D.析构函数在对象的生存期即将结束时被系统自动调用13.关于虚函数,下列说法不正确的是( C )A.虚函数是动态联编的基础B.虚函数的定义只能出现在类定义中的函数原形声明中C.类的成员函数均可声明为虚函数D.虚函数是用virtual关键字声明的非静态成员函数14.关于抽象类,下列说法不正确的是( B )A.抽象类不能实例化B.带有虚函数的类称为抽象类C.带有纯虚函数的类称为抽象类D.抽象类的作用是为一个类族建立一个公共接口15.下列对一维数组a的正确定义是( C )A.int n=5,a[n];B.int a(5);C.const int n=5;int a[n];D.int n;cin>>n;int a[n];16.下列数组定义语句中,不合法的是( A )A.int a[3]={0,1,2,3};B.int a[]={0,1,2};C.int a[3]={0,1,2};D.int a[3]={0};17.已知int a[10]={0,1,2,3,4,5,6,7,8,9}和*p=a,则不能表示数组a中元素的式子是( C )。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

C++箴言:多态基类中将析构函数声明为虚拟
有很多方法可以跟踪时间的轨迹,所以有必要建立一个 TimeKeeper 基类,并为不同的计时方法建立派生类:
class TimeKeeper {
public:
TimeKeeper();
~TimeKeeper();
...
};
class AtomicClock: public TimeKeeper { ... };
class WaterClock: public TimeKeeper { ... };
class WristWatch: public TimeKeeper { ... };
很多客户只是想简单地取得时间而不关心如何计算的细节,所以一个 factory 函数--返回一个指向新建派生类对象的基类指针的函数--被用来返回一个指向计时对象的指针:TimeKeeper* getTimeKeeper(); // returns a pointer to a dynamic-
// ally allocated object of a class
// derived from TimeKeeper
按照 factory 函数的惯例,getTimeKeeper 返回的对象是建立在堆上的,所以为了避免泄漏内存和其他资源,最重要的就是要让每一个返回的对象都可以被完全删除。

TimeKeeper *ptk = getTimeKeeper(); // get dynamically allocated object
// from TimeKeeper hierarchy
... // use it
delete ptk; // release it to avoid resource leak
现在我们精力集中于上面的代码中一个更基本的缺陷:即使客户做对了每一件事,也无法预知程序将如何运转。

问题在于 getTimeKeeper 返回一个指向派生类对象的指针(比如 AtomicClock),那个对象通过一个基类指针(也就是一个 TimeKeeper* 指针)被删除,而且这个基类
(TimeKeeper)有一个非虚的析构函数。

祸端就在这里,因为 C++ 指出:当一个派生类对象通过使用一个基类指针删除,而这个基类有一个非虚的析构函数,则结果是未定义的。

运行时比较有代表性的后果是对象的派生部分不会被销毁。

如果 getTimeKeeper 返回一个指向AtomicClock 对象的指针,则对象的 AtomicClock 部分(也就是在 AtomicClock 类中声明的数据成员)很可能不会被销毁,AtomicClock 的析构函数也不会运行。

然而,基类部分(也就是 TimeKeeper 部分)很可能已被销毁,这就导致了一个古怪的"部分析构"对象。

这是一个泄漏资源,破坏数据结构以及消耗大量调试时间的绝妙方法。

排除这个问题非常简单:给基类一个虚析构函数。

于是,删除一个派生类对象的时候就有了你所期望的正确行为。

将销毁整个对象,包括全部的派生类部分:
class TimeKeeper {
public:
TimeKeeper();
virtual ~TimeKeeper();
...
};
TimeKeeper *ptk = getTimeKeeper();
...
delete ptk; // now behaves correctly
类似 TimeKeeper 的基类一般都包含除了析构函数以外的其它虚函数,因为虚函数的目的就是允许派生类定制实现(参见 Item 34)。

例如,TimeKeeper 可能有一个虚函数getCurrentTime,在各种不同的派生类中有不同的实现。

几乎所有拥有虚函数的类差不多都应该有虚析构函数。

如果一个类不包含虚函数,这经常预示不打算将它作为基类使用。

当一个类不打算作为基类时,将析构函数声明为虚拟通常是个坏主意。

考虑一个表现二维空间中的点的类:class Point { // a 2D point
public:
Point(int xCoord, int yCoord);
~Point();
private:
int x, y;
};
如果一个 int 占 32 位,一个 Point 对象正好适用于 64 位的寄存器。

而且,这样一个 Point 对象可以被作为一个 64 位的量传递给其它语言写的函数,比如 C 或者FORTRAN。

如果 Point 的析构函数是虚拟的,情况就完全不一样了。

虚函数的实现要求对象携带额外的信息,这些信息用于在运行时确定该对象应该调用哪一个虚函数。

典型情况下,这一信息具有一种被称为 vptr(virtual table pointer,虚函数表指针)的指针的形式。

vptr 指向一个被称为 vtbl(virtual table,虚函数表)的函数指针数组,每一个包含虚函数的类都关联到 vtbl。

当一个对象调用了虚函数,实际的被调用函数通过下面的步骤确定:找到对象的 vptr 指向的 vtbl,然后在 vtbl 中寻找合适的函数指针。

虚函数如何被实现的细节是不重要的。

重要的是如果 Point 类包含一个虚函数,这个类型的对象的大小就会增加。

在一个 32 位架构中,它们将从 64 位(相当于两个 int)长到 96 位(两个 int 加上 vptr);在一个 64 位架构中,他们可能从 64 位长到 128 位,因为在这样的架构中指针的大小是 64 位的。

为 Point 加上 vptr 将会使它的大小增长50-100%!Point 对象不再适合 64 位寄存器。

而且,Point 对象在 C++ 和其他语言(比如 C)中,看起来不再具有相同的结构,因为其它语言缺乏 vptr 的对应物。

结果,Points 不再可能传入其它语言写成的函数或从其中传出,除非你为 vptr 做出明确的对应,而这是它自己的实现细节并因此失去可移植性。

相关文档
最新文档