如何强制调用类的全局实例的析构函数和构造函数(因此“重新初始化”类实例)

Posted

技术标签:

【中文标题】如何强制调用类的全局实例的析构函数和构造函数(因此“重新初始化”类实例)【英文标题】:How to force to call a destructor and constructor of a global instance of a class (so "re-init" the class instance) 【发布时间】:2019-08-02 08:27:01 【问题描述】:

我有一个用 C++ 开发的固件项目,其中所有驱动程序都是用一个类制作的,没有简单的方法来修改它们。

驱动程序用于 uP 的内部外围设备,由该类的全局实例实现;现在我必须修改该函数并允许在异常情况或类似情况下“重新初始化”驱动程序。

驱动程序的初始化是在驱动程序的构造函数中进行的(以这种方式实现,我无法修改它)并且没有显式方式(特定方法或类似方法)来调用该函数.所以我需要强制调用类的构造函数。 实例的所有信息丢失不是问题,所以可以删除实例并重新制作。

例如,部分代码类似于(来自 Mbed 库):

class SPI 

public:

    SPI(PinName mosi, PinName miso, PinName sclk, PinName ssel=NC);

    void format(int bits, int mode = 0);
[.....]

  ~SPI()


在代码的其他部分有一个全局实例:

SPI SPI_Master(P0_9, P0_8, P0_7);

void funcA(int b)

所以在函数中有一种方法可以做类似的事情:

void SPIException()
   delete SPI_Master;
   SPI_Master = new SPI (P0_9, P0_8, P0_7);

所以要强制调用构造函数?

还有一点澄清:

SPI SPI_Master(P0_9, P0_8, P0_7);

完全等同于:

SPI SPI_Master = 新 SPI(P0_9, P0_8, P0_7);

?

【问题讨论】:

析构函数几乎是一个可以显式调用的普通函数。事实上,如果您使用placement new,则要求显式调用析构函数(并且placement new 可能是关于如何“重新创建”对象的提示)。 其他可能解决这个问题的方法是有一个“干净”或“deinit”类型的函数,由析构函数调用,你也可以显式调用。或者可能不使用全局变量,让对象在超出范围或生命周期结束时以自然方式被破坏,或者作为异常堆栈展开的一部分。 使用放置构造函数。 ***.com/questions/222557/… 我想到了几个选项:1)您可以使用全局唯一指针而不是使用全局对象吗?这样底层的SPI可以很容易地重置2)显式调用析构函数并使用placement new在相同的内存地址中构造SPI 【参考方案1】:

析构函数只是一个具有特殊名称的函数。像实例一样调用它。~T()。

void destroy()

  SPI_Master.~SPI():

你可以通过placement new在给定位置强制构造一个对象。

void reinit()

  new(&SPI_Master) SPI(/*arguments go here*/);

【讨论】:

【参考方案2】:

详细说明SPD的提示:

2) 显式调用析构函数并使用placement new在同一内存地址构造SPI

我做了一个小样本:

#include <iostream>

struct Global 
  int a1, a2;
  Global(int a1, int a2): a1(a1), a2(a2)
  
    std::cout << "Global::Global(): a1: " << a1 << " a2: " << a2 << '\n';
  
  ~Global()
  
    std::cout << "Global::~Global()\n";
  
  Global(const Global&) = delete;
  Global& operator=(const Global&) = delete;
;

std::ostream& operator<<(std::ostream &out, const Global &global)

  return out
    << "&global: " << &global << '\n'
    << "global: global.a1: " << global.a1 << " global.a2: " << global.a2 << '\n';


Global global(123, 456);

int main()

  std::cout << "Initially: " << global;
  global.a1 = 321; global.a2 = 654;
  std::cout << "Changed: " << global;
  global.~Global();
  new(&global) Global(123, 456);
  std::cout << "Re-Inited: " << global;
  std::cout << "Exiting...\n";

输出:

Global::Global(): a1: 123 a2: 456
Initially: &global: 0x6013d8
global: global.a1: 123 global.a2: 456
Changed: &global: 0x6013d8
global: global.a1: 321 global.a2: 654
Global::~Global()
Global::Global(): a1: 123 a2: 456
Re-Inited: &global: 0x6013d8
global: global.a1: 123 global.a2: 456
Exiting...
Global::~Global()

Live Demo on coliru

请注意:

全局实例有其局限性,但它可能存在,并且不能出于任何原因更改。 (对于单例,应该提到Singleton Pattern,这也有助于解决static initialization order ‘fiasco’ (problem)?。)

一般来说,new 创建的东西通常被构建到分配在堆上的内存中(即,如果没有使用自定义 new),这与在其他地方创建的静态实例相反。

Placement new 可以在没有分配的情况下进行构造,即进入调用者提供的存储空间,即独立于存储空间的分配方式。

【讨论】:

【参考方案3】:

SPI SPI_Master(P0_9, P0_8, P0_7); 不等同于 SPI SPI_Master = new SPI(P0_9, P0_8, P0_7);

最后一行将不会被编译,因为new 返回指向位于堆中的对象的指针,但不是一个对象。同时SPI SPI_Master(P0_9, P0_8, P0_7); 位于堆栈中。您可以额外阅读有关内存类型的信息。 正确的形式是: SPI * SPI_Master = new SPI(P0_9, P0_8, P0_7); 是的,这是您可以使用的解决方案之一。

在你提到的函数之后的某个地方将按预期工作:

void SPIException()
   delete SPI_Master;
   SPI_Master = new SPI (P0_9, P0_8, P0_7);

但它会在所有使用 SPI_Master 的代码中调用更改:从 SPI_Master.anyCall()SPI_Master-&gt;anyCall()

如果您无法换行: SPI SPI_Master(P0_9, P0_8, P0_7);

你可以尝试显式调用析构函数而不是覆盖相同的变量:

SPI_Master.~SPI(); 

SPI_Master = SPI(P0_9, P0_8, P0_7);

但要小心: 1)这取决于构造函数和析构函数体的真正作用。 2)分配期间的破坏和建造顺序将生成您的编译器: - 创建新的,删除旧的并分配新的 - 删除旧的,创建新的,分配新的

所以这个解决方案可能非常危险。

【讨论】:

您的最后几位不正确。您需要使用placement new 在与SPI_Master 相同的位置显式构造另一个对象。 operator =() 可能会也可能不会正常工作,因为 lhs 是一个被破坏的对象。【参考方案4】:

使用全局指针指向 SPI_Master。像这样:

SPI* SPI_Master;

// in some init code:
SPI_Master = new SPI .....

那么你的 SPIException 就可以照写使用了。

【讨论】:

以上是关于如何强制调用类的全局实例的析构函数和构造函数(因此“重新初始化”类实例)的主要内容,如果未能解决你的问题,请参考以下文章

继承和组合混搭的情况下,构造和析构函数的调用顺序

mfc 类的析构函数

C++中,子类会继承父类的虚函数表!对于父类的析构函数(虚函数) 也会继承吗?

子类的构造函数,子类的析构函数,子类型关系

虚析构函数与纯虚函数

虚函数本质