类层次结构的一个非常常见的错误是将基类中的方法指定为虚拟方法,以便 全部 在继承链中重写以做一些工作,并忘记将调用传播到基本实现。
示例场景
class Container
{
public:
virtual void PrepareForInsertion(ObjectToInsert* pObject)
{
// Nothing to do here
}
};
class SpecializedContainer : public Container
{
protected:
virtual void PrepareForInsertion(ObjectToInsert* pObject)
{
// Set some property of pObject and pass on.
Container::PrepareForInsertion(pObject);
}
};
class MoreSpecializedContainer : public SpecializedContainer
{
protected:
virtual void PrepareForInsertion(ObjectToInsert* pObject)
{
// Oops, forgot to propagate!
}
};
我的问题是:是否有一种好的方法/模式来确保始终在调用链的末尾调用基本实现?
我知道有两种方法可以做到这一点。
方法一
您可以将成员变量用作标志,在虚方法的基本实现中将其设置为正确的值,并在调用后检查其值。这需要使用公共(public)的非虚拟方法作为客户端的接口(interface),并使虚拟方法受到保护(这实际上是一件好事),但它需要专门为此目的使用成员变量(这需要如果虚方法必须是 const 则可变)。
class Container
{
public:
void PrepareForInsertion(ObjectToInsert* pObject)
{
m_callChainCorrect = false;
PrepareForInsertionImpl(pObject);
assert(m_callChainCorrect);
}
protected:
virtual void PrepareForInsertionImpl(ObjectToInsert* pObject)
{
m_callChainCorrect = true;
}
private:
bool m_callChainCorrect;
};
class SpecializedContainer : public Container
{
protected:
virtual void PrepareForInsertionImpl(ObjectToInsert* pObject)
{
// Do something and pass on
Container::PrepareForInsertionImpl(pObject);
}
};
方法二
另一种方法是用不透明的“cookie”参数替换成员变量并做同样的事情:
class Container
{
public:
void PrepareForInsertion(ObjectToInsert* pObject)
{
bool callChainCorrect = false;
PrepareForInsertionImpl(pObject, &callChainCorrect);
assert(callChainCorrect);
}
protected:
virtual void PrepareForInsertionImpl(ObjectToInsert* pObject, void* pCookie)
{
*reinrepret_cast<bool*>(pCookie) = true;
}
};
class SpecializedContainer : public Container
{
protected:
virtual void PrepareForInsertionImpl(ObjectToInsert* pObject, void* pCookie)
{
// Do something and pass on
Container::PrepareForInsertionImpl(pObject, pCookie);
}
};
在我看来,这种方法不如第一种,但它确实避免了使用专用成员变量。
还有哪些其他可能性?
最佳答案
您已经想出了一些聪明的方法来做到这一点,但(正如您所承认的)使类膨胀并添加解决对象职责而不是程序员缺陷的代码的代价。
真正的答案是不要在运行时这样做。这是程序员错误,而不是运行时错误。
在编译时执行:如果语言支持,则使用语言结构,或者使用模式来强制执行它(例如,模板方法),或者使您的编译依赖于通过的测试,并设置测试来强制执行它。
或者,如果未能传播导致派生类失败,则让它失败,并带有异常消息,通知派生类的作者他未能正确使用基类。
关于c++ - 如何确保虚方法调用一直传播到基类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63640649/