c++ - 如何防止对正在被销毁的子类实例进行调用函数调用

标签 c++ multithreading

我只需要修复一个难以发现的错误,但我对修复并不满意。我们的应用程序是在 Windows 中运行的 C++,错误是纯粹的虚拟调用崩溃。这是一些背景:

class Observable
{
public:
    Observable();
    virtual ~Observable();
    void Attach(Observer&); // Seize lock, then add Observer to the list
    void Detach(Observer&); // Seize lock, then remove Observer from the list
    void Notify(); // Seize lock, then iterate list and call Update() on each Observer

protected:
    // List of attached observers
};

class Subject : public Observable
{
    // Just important to know there is a subclass of Observable
}

class Observer
{
public:
    Observer();
    virtual ~Observer(); // Detaches itself from the Observable
    void Update() = 0;
};

class Thing : public Observer
{
public:
    void Update(); // What it does is immaterial to this question
};

因为这是一个多线程环境,所以 Attach()、Detach() 和 Notify() 都有锁。 Notify() 获取锁,然后迭代观察者列表并对每个观察者调用 Update()。

(我希望这足以提供背景知识,而无需发布完整的代码正文。)

当观察者被摧毁时,问题就出现了。销毁时,观察者将自身与可观察对象分离。同时,在另一个线程中,正在对主题调用 Notify()。我最初的想法是,由于 Detach()(在销毁观察者时调用)和 Notify() 中的锁,我们受到了保护。然而,C++ 首先销毁子类,然后再销毁基类。这意味着,在获得 Detach() 中的锁(这会阻止 Notify() 函数继续执行)之前,纯虚拟 Update() 函数的实现已被破坏。 Notify() 函数继续(因为它已经获得了锁)并尝试调用 Update()。结果是崩溃。

现在,这是我的解决方案,它有效,但给我一种恶心的感觉。我将 Update() 函数从纯虚拟更改为虚拟,并提供了一个不执行任何操作的主体。这让我困扰的原因是 Update() 仍在被调用,但是是在部分破坏的对象上。换句话说,我侥幸逃脱了一些惩罚,但我并不热衷于实现。

讨论的其他选项:

1) 将锁定移至子类中。不可取,因为它迫使每个子类的开发人员重复逻辑。如果他忽略了锁定,就会发生不好的事情。

2) 通过 Destroy() 函数强制销毁观察者。老实说,我不确定如何为基于堆栈的对象实现此功能。

3) 让子类在其析构函数中调用“PreDestroy()”函数来通知基类即将销毁。我不知道如何强制执行此操作,并且忘记它可能会导致难以发现的运行时错误。

任何人都可以提供有关更好的方法来防止此类崩溃的建议吗?我有一种不愉快的感觉,我想念房间里的大象。

JAB

最佳答案

这个问题说明了多线程设计的一个更普遍的结果:受多线程影响的对象不能保证在任何时间点都不会并发访问自身。这个结果就是房间里的大象,它给你带来了你在问题末尾所描述的不愉快的感觉。

简而言之,您的问题是 Attach()Detach()Notify() 负责获取适当的锁定并做他们的事情。他们需要能够假设在调用之前已获取锁。

不幸的是,该解决方案需要更复杂的设计。有必要使用一些独立于对象(或类)的单一源来协调所有对象的构造、更新(包括附加和分离)和销毁。并且有必要防止任何这些过程独立于中介者而发生。

无论您是通过技术手段(例如访问控制等)来阻止这些进程,还是只是声明所有类型都必须遵守的策略,这都是一种设计选择。该选择取决于您是否可以依赖您的开发人员(包括您自己)遵守政策准则。

关于c++ - 如何防止对正在被销毁的子类实例进行调用函数调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36484706/

相关文章:

c++ - 类中静态成员的构造顺序

c++ - 无法访问功能

c++ - 无论如何传递函数作为模板参数?

c++ - 为什么我的默认参数被忽略?

android - 在 Android 后台保持蓝牙连接?

Python - 如何从不同的方法触发方法中的操作

c++ - 指定给 RtlValidateHeap DLL 的地址无效

java - 如何让两个线程通过处理程序进行通信?

c++ - 变量中的不同值

c++ - 简单的 C++ pthread 程序输出与预期不符