替代基础析构函数上的虚拟函数调用

Alternate to virtual function call on base destructor

本文关键字:虚拟 函数调用 析构函数      更新时间:2023-10-16

所以,我写了下面的代码。

#include <iostream>
class AbstractMachine
{
public:
void powerOn();
void powerOff();
bool isPoweredOn();
virtual ~AbstractMachine();
protected:
virtual void powerOnImpl() = 0;
virtual void powerOffImpl() = 0;
private:
bool m_isPoweredOn;
};
bool AbstractMachine::isPoweredOn()
{
return m_isPoweredOn;
}
void AbstractMachine::powerOn()
{
if (!m_isPoweredOn)
{
powerOnImpl();
m_isPoweredOn = true;
}
}
void AbstractMachine::powerOff()
{
if (m_isPoweredOn)
{
powerOffImpl();
m_isPoweredOn = false;
}
}
AbstractMachine::~AbstractMachine()
{
powerOff();       // (1)
std::cout << "Destroying a machine" << std::endl;
}
class AirConditioner : public AbstractMachine
{
protected:
void powerOnImpl() override;
void powerOffImpl() override;
public:
~AirConditioner();
};
void AirConditioner::powerOnImpl()
{
std::cout << "Turning on air conditioner" << std::endl;
}
void AirConditioner::powerOffImpl()
{
std::cout << "Turning off air conditioner" << std::endl;
}
AirConditioner::~AirConditioner()
{
//powerOff();        // (2)
std::cout << "Destroing air conditioner" << std::endl;
}

int main()
{
AbstractMachine *machine = new AirConditioner();
machine->powerOn();
delete machine;
}

这将失败,因为当基析构函数调用派生函数 (1) 时,派生对象已经被销毁。

当我评论 (1) 和取消评论 (2) 时,这运行良好,但我想在这里做的是在机器被破坏之前自动且始终关闭机器,尽管"关闭电源"操作取决于自定义机器

有没有更好的方法? 我应该放弃吗? 或者它甚至不是OOP设计的正确方式?

正如您所观察到的,当基类构造函数运行时,派生类部分不再存在。 没有技巧可以做到这一点,所以没有办法在基本析构函数中(或从)完成所有操作。

您只需要弄清楚关闭的哪个部分属于每个级别,并让每个析构函数负责自己的级别。 当对象被销毁时,每个关卡的析构函数都会从下到上运行,所以一切都完成了。 我认为这将是 OOP 方法。

几个观察结果:

  1. 如果您可能在析构函数中运行复杂的逻辑,则通常值得退后一步。由于AbstractMachine不知道powerOffImpl()会执行什么逻辑,程序员不能确定派生类不会抛出异常,析构函数应该始终避免抛出异常。对于AirConditioner来说,最好直接在其析构函数中调用powerOffImpl(),然后AirConditioner的实现者知道任何异常风险,并可以编写代码来缓解这种情况。此外,如果这样做没有意义,实现者可以选择不调用powerOffImpl()

  2. 继承是类之间非常强大的耦合形式。当您将函数实现放在基类中时,您会对派生类应该如何操作做出强有力的假设,这些假设以后很难更改。例如,在这种情况下,我们不能尝试两次打开机器电源。我们能确定情况总是如此吗? 例如,我的手机在已经通电时按下开机按钮时会执行某些操作。通常最好从纯接口继承(例如IMachine),以便具体类可以自己做出这些决策,这也避免了虚方法问题。

  3. 有时在这里重新考虑模型也是一个好主意。"摧毁"机器是什么意思?当我"销毁"一台真实世界的机器时,它可能没有能力运行完整的断电序列,那么虚拟机是否应该在析构函数中调用powerOffImpl()?另一种考虑方式是考虑机器的使用寿命可能与开关寿命不同。它们是不同的东西,所以也许它们应该以不同的方式建模。在这种情况下,我会考虑使用一个单独的MachineSession对象来模拟开关寿命。MachineSession构造函数获取IMachine的实例并执行IMachine.powerOn(),并在其析构函数中调用IMachine.powerOff()(假设 powerOff 给出了 noexcept 保证,或者你用try/catch保护它)。这也避免了m_isPoweredOn,因为如果您有有效的MachineSession那么您就知道机器已通电。