クラス階層で非常によくある間違いの 1 つは、継承チェーン内のすべてのオーバーライドが何らかの作業を行うために、基本クラスのメソッドを仮想として指定し、呼び出しを基本実装に伝達するのを忘れることです。
シナリオ例
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!
}
};
私の質問は次のとおりです。基本実装が常に呼び出しチェーンの最後で呼び出されるようにするための良い方法/パターンはありますか?
これを行うための2つの方法を知っています。
方法 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
もう 1 つの方法は、メンバー変数を不透明な「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);
}
};
私の意見では、このアプローチは最初のアプローチよりも劣っていますが、専用のメンバー変数の使用を回避しています。
他にどのような可能性がありますか?