如何强制调用类的全局实例的析构函数和构造函数(因此“重新初始化”类实例)
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->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 就可以照写使用了。
【讨论】:
以上是关于如何强制调用类的全局实例的析构函数和构造函数(因此“重新初始化”类实例)的主要内容,如果未能解决你的问题,请参考以下文章