One very common mistake with class hierarchies is to specify a method in a base class as being virtual, in order for all overrides in the inheritance chain to do some work, and forgetting to propagate the call on to base implementations.
Example scenario
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!
}
};
My question is: is there a good way/pattern to ensure that the base implementation always gets called at the end of the call chain?
I know of two methods to do this.
Method 1
You can use a member variable as a flag, set it to the correct value in the base implementation of the virtual method, and check its value after the call. This requires to use a public non-virtual method as interface for the clients, and making the virtual method protected (which is actually a good thing to do), but it requires the use of a member variable specifically for this purpose (which needs to be mutable if the virtual method must be 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);
}
};
Method 2
The other way to do it is to replace the member variable with an opaque "cookie" parameter and do the same thing:
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);
}
};
This approach is inferior to the first one in my opinion, but it does avoid the use of a dedicated member variable.
What other possibilities are there?
See Question&Answers more detail:
os 与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…