类层次结构的一个非常常见的错误是将基类中的方法指定为虚拟方法,以便继承链中的所有覆盖都做一些工作,而忘记将调用传播到基实现。
示例场景
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!
}
};
我的问题是:是否有一种好的方法/模式来确保始终在调用链的末尾调用基本实现?
我知道有两种方法可以做到这一点。
方法一
您可以将成员变量用作标志,在虚方法的基本实现中将其设置为正确的值,并在调用后检查其值。这需要使用公共的非虚拟方法作为客户端的接口,并使虚拟方法受到保护(这实际上是一件好事),但它需要专门为此目的使用成员变量(这需要如果虚方法必须是 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);
}
};
在我看来,这种方法不如第一种,但它确实避免了使用专用成员变量。
还有哪些其他可能性?