C++ 纯虚析构函数
在本文中,我们将讨论 C++ 中的纯虚析构函数。 然而,要完全掌握这个概念,我们必须了解不同的相关概念。
首先,我们将看到使用基类指针调用派生类函数的方法,然后我们将讨论继承中的析构函数调用问题。 最后,我们将讨论纯虚析构函数及其实现问题。
使用基类指针调用派生类函数
基类指针有权指向它的任何派生类对象。 这使我们免于声明和管理多个派生类指针来处理不同的派生类对象。
但是,基类指针一般不能调用派生类的方法。 例如,请参见以下代码:
#include<iostream>
using namespace std;
class P{};
class C:public P{
public:
void f(){ cout << "Function C\n"; }
};
int main(){
P *ptr = new C;
ptr->f();
return 0;
}
编译时,此代码显示以下错误:
virtual_destructor0.cpp: In function ‘int main()’:
virtual_destructor0.cpp:12:7: error: ‘class P’ has no member named ‘f’
ptr->f();
如果我们在基类中声明相同的函数,编译器会调用基类函数而不是派生类函数。 看下一段代码,我们在类 P 中添加了函数 f:
#include<iostream>
using namespace std;
class P{
public:
void f(){ cout << "Function P\n"; }
};
class C:public P{
public:
void f(){ cout << "Function C\n"; }
};
int main(){
P *ptr = new C;
ptr->f();
return 0;
}
添加函数 f 后,查看输出:
Function P
这意味着基类指针调用基类函数,而不是派生类函数。 我们推导出类指针可以从其类而不是包含对象的类调用函数的规则。
但是,还有另一个至关重要的概念,称为虚函数。 虚函数允许派生类重写基类函数,基类指针可以调用。
函数覆盖是指派生类为相应的基类函数提供自己的功能。 基类在其函数中写入关键字 virtual 以允许在派生类中进行重写。
再次,看代码:
#include<iostream>
using namespace std;
class P{
public:
virtual void f(){ cout << "Function P\n"; }
};
class C:public P{
public:
void f(){ cout << "Function C\n"; }
};
int main(){
P *ptr = new C;
ptr->f();
return 0;
}
注意在基类函数 f() 之前添加了 virtual
关键字。 新增后,看输出:
Function C
这意味着基类指针现在正在根据这个基类指针引用的对象类型调用一个函数(即派生类的成员将执行)。 请记住,早期的行为是调用指针类型的成员(即基类)。
但是,派生类实现者可以选择为虚类提供功能,这意味着派生类可能会也可能不会覆盖虚拟基函数。
有时,类的架构师(设计者)强制派生类来实现基类的功能,这涉及到抽象基类(也称为ABC)和纯虚函数等更多概念。
我们将很快讨论它们。 如果需要,您可以访问引用的网站。
纯虚函数和抽象类
基类可以声明一个纯虚函数来强制在派生类中实现虚基方法。 纯虚函数没有实现。
此外,具有一个或多个纯虚函数的类成为抽象类。 抽象类是不可实例化的(但是,我们可以声明它们的指针,指向派生类对象)。
纯虚函数的语法略有不同:
virtual void f()=0;
纯虚函数没有定义或函数体; 因此,它是不可调用的。 但是,它强制派生类实现纯虚函数。
否则,派生类变为抽象类(即不可实例化)。
相反,要使一个类抽象,我们只需要做一个抽象函数。 我们将在本文后面提到这句话。
虚拟和纯虚拟析构函数
首先,请注意析构函数不能被覆盖。 但是,要同时调用基类和子类的析构函数,指针(基)类中的析构函数应该是虚拟的。
否则,只会调用子类的析构函数。 例如,让我们看一下下面的代码:
#include<iostream>
using namespace std;
class P{
public:
~P(){ cout << "P Class Destructor\n"; }
};
class C: public P{
public:
~C(){ cout << "C Class Destructor\n"; }
};
int main(){
P *ptr = new C;
delete ptr;
return 0;
}
输出结果如下:
P Class Destructor
在这里,可以看到指针是P类的,对象是C类的。delete
操作只调用了基类的析构函数,这意味着如果我们要删除派生类的资源,我们是做不到的。
为此,我们必须在基类中声明一个虚析构函数。 如您所知,为此,我们必须在基类析构函数中添加一个 virtual
关键字。
这是示例代码:
#include<iostream>
using namespace std;
class P{
public:
virtual ~P(){ cout << "P Class Destructor\n"; }
};
class C: public P{
public:
~C(){ cout << "C Class Destructor\n"; }
};
int main(){
P *ptr = new C;
delete ptr;
return 0;
}
输出结果如下:
C Class Destructor
P Class Destructor
纯虚拟析构函数
最后进入正题,通过纯虚析构函数来解决调用父类和派生类的析构函数的问题。
如前所述,析构函数不同于其他函数,没有重写析构函数的概念。 因此,纯虚析构函数不能强制派生类实现析构函数。
现在,让我们换个角度来看事情。 要使一个类抽象,我们必须声明至少一个纯虚函数。
在这种情况下,派生类将绑定实现纯虚函数成为具体类。 因此,我们通过在此类中声明一个纯虚析构函数来使类抽象,这不会强制派生类实现任何东西。
纯虚拟析构函数的几率
永远记住没有免费的午餐定理; 声明一个纯虚拟析构函数有一些复杂性。
让我们通过以下示例了解这些:
class P{
public:
virtual ~P()=0;
};
如果我们编译这段代码,会出现以下错误:
/tmp/ccZfsvAh.o: In function `C::~C()':
virtual_destructor4.cpp:(.text._ZN1CD2Ev[_ZN1CD5Ev]+0x22): undefined reference to `P::~P()'
collect2: error: ld returned 1 exit status
幸运的是,世界上大多数复杂问题都有简单的解决方案。 出现此错误是因为编译器正在寻找析构函数的定义,而该定义不可用。
因此,解决方案是定义纯虚析构函数。 似乎矛盾; 编译器要求定义纯虚函数?
是的,这是 C++ 的一些其他优势之一。 我们不能在类内部实现纯虚析构函数; 但是,我们可以在类之外实现它。
看下面的代码:
class P{
public:
virtual ~P()=0;
};
P::~P(){ cout << "P Class Destructor\n"; }
最后一行,我们在类外定义了一个纯虚析构函数,不会出现编译错误。 此外,我们将能够调用基类和派生类的析构函数:
C Class Destructor
P Class Destructor
总而言之,要创建一个抽象基类,我们可以在其中声明一个纯虚析构函数; 但是,我们必须在类之外定义这个纯虚拟析构函数(我们可以在其中清理任何资源/内存)。 这样,我们就可以同时调用基类和派生类的析构函数。
相关文章
在 C++ 中通过掷骰子生成随机值
发布时间:2023/04/09 浏览次数:169 分类:C++
-
本文解释了如何使用时间因子方法和模拟 C++ 中的掷骰子的任意数方法生成随机数。了解它是如何工作的以及它包含哪些缺点。提供了一个 C++ 程序来演示伪数生成器。
在 C++ 中使用模板的链表
发布时间:2023/04/09 浏览次数:158 分类:C++
-
本文解释了使用模板在 C++ 中创建链表所涉及的各个步骤。工作程序演示了一个链表,该链表使用模板来避免在创建新变量时声明数据类型的需要。
在 C++ 中添加定时延迟
发布时间:2023/04/09 浏览次数:142 分类:C++
-
本教程将为你提供有关在 C++ 程序中添加定时延迟的简要指南。这可以使用 C++ 库为我们提供的一些函数以多种方式完成。
在 C++ 中创建查找表
发布时间:2023/04/09 浏览次数:155 分类:C++
-
本文重点介绍如何创建查找表及其在不同场景中的用途。提供了三个代码示例以使理解更容易,并附有代码片段以详细了解代码。
如何在 C++ 中把字符串转换为小写
发布时间:2023/04/09 浏览次数:63 分类:C++
-
介绍了如何将 C++ std::string 转换为小写的方法。当我们在考虑 C++ 中的字符串转换方法时,首先要问自己的是我的输入字符串有什么样的编码
如何在 C++ 中确定一个字符串是否是数字
发布时间:2023/04/09 浏览次数:163 分类:C++
-
本文介绍了如何检查给定的 C++ 字符串是否是数字。在我们深入研究之前,需要注意的是,以下方法只与单字节字符串和十进制整数兼容。
如何在 c++ 中查找字符串中的子字符串
发布时间:2023/04/09 浏览次数:65 分类:C++
-
本文介绍了在 C++ 中检查一个字符串是否包含子字符串的多种方法。使用 find 方法在 C++ 中查找字符串中的子字符串
如何在 C++ 中把字符串转换为 Char 数组
发布时间:2023/04/09 浏览次数:107 分类:C++
-
本文介绍了在 C++ 中把字符串转换为 char 数组的多种方法。使用 std::basic_string::c_str 方法将字符串转换为 char 数组