- Java 双重比较
- java - 比较器与 Apache BeanComparator
- Objective-C 完成 block 导致额外的方法调用?
- database - RESTful URI 是否应该公开数据库主键?
我不得不修复一个难以发现的错误,但我对这个修复并不满意。我们的应用程序是在 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()。
(我希望这足以作为背景介绍,而不必发布完整的代码。)
当一个观察者被摧毁时,问题就出现了。在销毁时,Observer 从 Observable 中分离出来。同时,在另一个线程中,正在对 Subject 调用 Notify()。我最初的想法是,由于 Detach()(在销毁 Observer 时调用)和 Notify() 中的锁,我们受到了保护。但是,C++ 先销毁子类,然后再销毁基类。这意味着,在获得阻止 Notify() 函数继续执行的 Detach() 中的锁之前,纯虚拟 Update() 函数的实现被破坏了。 Notify() 函数继续(因为它已经获得了锁)并尝试调用 Update()。结果是崩溃。
现在,这是我的解决方案,它有效,但给我一种反胃的感觉。我将 Update() 函数从纯虚拟更改为虚拟,并提供了一个什么都不做的主体。这让我感到困扰的原因是 Update() 仍在被调用,但是是在一个部分破坏的对象上。换句话说,我正在逃避某些事情,但我对实现并不狂热。
讨论的其他选项:
1) 将锁定移动到子类中。不可取,因为它迫使每个子类的开发人员重复逻辑。如果他忽略了锁定,就会发生不好的事情。
2) 通过 Destroy() 函数强制销毁观察者。老实说,我不确定如何为基于堆栈的对象实现这一点。
3) 让子类在其析构函数中调用“PreDestroy()”函数以通知基类即将销毁。我不知道如何强制执行此操作,忘记它可能会导致难以发现的运行时错误。
任何人都可以就防止此类崩溃的更好方法提出任何建议吗?我有一种不愉快的感觉,我想念房间里的大象。
刺拳
最佳答案
这个问题说明了多线程设计的一个更普遍的结果:受多线程影响的任何对象都不能保证在任何时间点都不能同时访问它自己。这个后果就是房间里的大象,它给您带来了您在问题末尾描述的不愉快的感觉。
简而言之,您的问题是 Attach()
、Detach()
和 Notify()
都负责抓取适当的锁定并做他们的事情。他们需要能够假设锁在被调用之前已被获取。
不幸的是,该解决方案需要更复杂的设计。一些独立于您的对象(或类)的单一来源有必要调解所有对象的构造、更新(包括附加和分离)和销毁。并且有必要防止任何这些过程独立于调解器发生。
无论您是通过技术手段(例如访问控制等)阻止这些进程,还是简单地声明所有类型都必须遵守的策略,都是一种设计选择。该选择取决于您是否可以依靠您的开发人员(包括您自己)来遵循政策指南。
关于c++ - 如何防止调用函数调用正在被销毁的子类的实例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36484706/
C语言sscanf()函数:从字符串中读取指定格式的数据 头文件: ?
最近,我有一个关于工作预评估的问题,即使查询了每个功能的工作原理,我也不知道如何解决。这是一个伪代码。 下面是一个名为foo()的函数,该函数将被传递一个值并返回一个值。如果将以下值传递给foo函数,
CStr 函数 返回表达式,该表达式已被转换为 String 子类型的 Variant。 CStr(expression) expression 参数是任意有效的表达式。 说明 通常,可以
CSng 函数 返回表达式,该表达式已被转换为 Single 子类型的 Variant。 CSng(expression) expression 参数是任意有效的表达式。 说明 通常,可
CreateObject 函数 创建并返回对 Automation 对象的引用。 CreateObject(servername.typename [, location]) 参数 serv
Cos 函数 返回某个角的余弦值。 Cos(number) number 参数可以是任何将某个角表示为弧度的有效数值表达式。 说明 Cos 函数取某个角并返回直角三角形两边的比值。此比值是
CLng 函数 返回表达式,此表达式已被转换为 Long 子类型的 Variant。 CLng(expression) expression 参数是任意有效的表达式。 说明 通常,您可以使
CInt 函数 返回表达式,此表达式已被转换为 Integer 子类型的 Variant。 CInt(expression) expression 参数是任意有效的表达式。 说明 通常,可
Chr 函数 返回与指定的 ANSI 字符代码相对应的字符。 Chr(charcode) charcode 参数是可以标识字符的数字。 说明 从 0 到 31 的数字表示标准的不可打印的
CDbl 函数 返回表达式,此表达式已被转换为 Double 子类型的 Variant。 CDbl(expression) expression 参数是任意有效的表达式。 说明 通常,您可
CDate 函数 返回表达式,此表达式已被转换为 Date 子类型的 Variant。 CDate(date) date 参数是任意有效的日期表达式。 说明 IsDate 函数用于判断 d
CCur 函数 返回表达式,此表达式已被转换为 Currency 子类型的 Variant。 CCur(expression) expression 参数是任意有效的表达式。 说明 通常,
CByte 函数 返回表达式,此表达式已被转换为 Byte 子类型的 Variant。 CByte(expression) expression 参数是任意有效的表达式。 说明 通常,可以
CBool 函数 返回表达式,此表达式已转换为 Boolean 子类型的 Variant。 CBool(expression) expression 是任意有效的表达式。 说明 如果 ex
Atn 函数 返回数值的反正切值。 Atn(number) number 参数可以是任意有效的数值表达式。 说明 Atn 函数计算直角三角形两个边的比值 (number) 并返回对应角的弧
Asc 函数 返回与字符串的第一个字母对应的 ANSI 字符代码。 Asc(string) string 参数是任意有效的字符串表达式。如果 string 参数未包含字符,则将发生运行时错误。
Array 函数 返回包含数组的 Variant。 Array(arglist) arglist 参数是赋给包含在 Variant 中的数组元素的值的列表(用逗号分隔)。如果没有指定此参数,则
Abs 函数 返回数字的绝对值。 Abs(number) number 参数可以是任意有效的数值表达式。如果 number 包含 Null,则返回 Null;如果是未初始化变量,则返回 0。
FormatPercent 函数 返回表达式,此表达式已被格式化为尾随有 % 符号的百分比(乘以 100 )。 FormatPercent(expression[,NumDigitsAfterD
FormatNumber 函数 返回表达式,此表达式已被格式化为数值。 FormatNumber( expression [,NumDigitsAfterDecimal [,Inc
我是一名优秀的程序员,十分优秀!