Одна очень распространенная ошибка с иерархиями классов заключается в том, чтобы указать метод в базовом классе как виртуальный, чтобы переопределить all в цепочке наследования, чтобы выполнить некоторую работу, и забыть распространять вызов на базовые реализации.
Пример сценария
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!
}
};
Мой вопрос: есть хороший способ/шаблон, чтобы гарантировать, что базовая реализация всегда вызывается в конце цепочки вызовов?
Я знаю два метода для этого.
Способ 1
Вы можете использовать переменную-член как флаг, установить его в правильное значение в базовой реализации виртуального метода и проверить его значение после вызова. Для этого требуется использовать публичный не виртуальный метод в качестве интерфейса для клиентов и сделать виртуальный метод защищенным (что действительно полезно делать), но для этого требуется использование переменной-члена специально для этой цели (которая должна быть изменчивым, если виртуальный метод должен быть 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);
}
};
Способ 2
Другой способ сделать это - заменить переменную-член непрозрачным параметром "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);
}
};
Этот подход уступает первому, на мой взгляд, но он избегает использования выделенной переменной-члена.
Какие еще существуют возможности?