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
总而言之,要创建一个抽象基类,我们可以在其中声明一个纯虚析构函数; 但是,我们必须在类之外定义这个纯虚拟析构函数(我们可以在其中清理任何资源/内存)。 这样,我们就可以同时调用基类和派生类的析构函数。
相关文章
Arduino 复位
发布时间:2024/03/13 浏览次数:315 分类:C++
-
可以通过使用复位按钮,Softwarereset 库和 Adafruit SleepyDog 库来复位 Arduino。
Arduino 的字符转换为整型
发布时间:2024/03/13 浏览次数:181 分类:C++
-
可以使用简单的方法 toInt()函数和 Serial.parseInt()函数将 char 转换为 int。
Arduino 串口打印多个变量
发布时间:2024/03/13 浏览次数:381 分类:C++
-
可以使用 Serial.print()和 Serial.println()函数在串口监视器上显示变量值。