用类层次结构一个非常常见的错误是指定在基类中的方法为虚,为了继承链中所有覆盖做了一些工作,而忘记传播到基本实现通话。
示例方案
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
您可以使用一个成员变量作为一个标志,将其设置为基实现虚拟方法的正确值,并调用后检查其值。 这需要使用公共非虚方法,接口,为客户和使受保护的虚拟方法(实际上是做了一件好事),但它需要使用一个成员变量的专门用于此目的(这需要是可变的,如果虚拟方法必须是常数)。
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);
}
};
这种方法不如在我看来,第一个,但它避免了使用专用的成员变量。
有什么其他的可能性有没有?
你想出一些巧妙的方法来做到这一点,在(你确认)腹胀类并补充说,不解决对象的责任,但程序员deficiences代码的成本。
真正的答案是不是要在运行时做到这一点。 这是一个程序员的错误,而不是运行时错误。
这样做在编译时:使用一种语言构造,如果语言支持它,或者使用模式的强制执行它( 例如 ,模板方法),或让你的编译依赖于通过的测试,并成立了测试,以强制执行。
或者,如果没有传播导致派生类中失败了,让它失败,与通知他未能正确使用基类派生类的作者的异常信息。
你要找的是简单的非虚拟接口模式。
它类似于你在这里做什么,但基类的实现可以保证被调用,因为它是唯一可以被称为实现。 它消除了,上面你的例子所需要的混乱。 并通过基类的调用是自动的,所以衍生版本不需要进行显式调用。
谷歌“非虚拟接口”的说明。
编辑 :查找“模板方法模式”后,我看到它对于非虚拟接口的另一个名字。 我从来没有听说过它的名字之前提到(我不完全GoF的歌迷俱乐部的正牌成员)。 就个人而言,我更喜欢名非虚接口,因为这个名字本身实际上描述的模式是什么。
再次编辑 :这里是这样做的NVI方式:
class Container
{
public:
void PrepareForInsertion(ObjectToInsert* pObject)
{
PrepareForInsertionImpl(pObject);
// If you put some base class implementation code here, then you get
// the same effect you'd get if the derived class called the base class
// implementation when it's finished.
//
// You can also add implementation code in this function before the call
// to PrepareForInsertionImpl, if you want.
}
private:
virtual void PrepareForInsertionImpl(ObjectToInsert* pObject) = 0;
};
class SpecializedContainer : public Container
{
private:
virtual void PrepareForInsertionImpl(ObjectToInsert* pObject)
{
// Do something and return to the base class implementation.
}
};
如果只有一个级别继承您可以使用模板方法模式 ,其中公共接口是非虚拟和调用虚函数的实现。 那么基本的逻辑是其中放心地被称为公共职能。
如果你有继承不止一个级别,并希望每个类调用基类,那么你仍然可以使用模板方法模式,但与一捻, 使虚拟函数的返回值只能通过施工的 base
,从而derived
将被强制调用基执行以返回一个值(在编译时执行)。
这并不强制每个类调用其直接基类,它可以跳过一个级别(我想不出来强制执行的好方法),但它确实迫使程序员有意识地决定,在它的工作原理换句话说针对疏忽没有恶意。
class base {
protected:
class remember_to_call_base {
friend base;
remember_to_call_base() {}
};
virtual remember_to_call_base do_foo() {
/* do common stuff */
return remember_to_call_base();
}
remember_to_call_base base_impl_not_needed() {
// allow opting out from calling base::do_foo (optional)
return remember_to_call_base();
}
public:
void foo() {
do_foo();
}
};
class derived : public base {
remember_to_call_base do_foo() {
/* do specific stuff */
return base::do_foo();
}
};
如果需要的public
(非virtual
)函数返回一个值的内virtual
一个应返回std::pair<
返回型 , remember_to_call_base>
需要注意的事项:
-
remember_to_call_base
宣布私人所以只有它一个明确的构造函数friend
(在这种情况下, base
)可以创建这个类的一个新实例。 -
remember_to_call_base
没有一个明确定义的拷贝构造函数所以编译器会创建一个与public
可访问性。它可以根据值从它返回base
执行。 -
remember_to_call_base
在申报protected
的部分base
,如果这是在private
部分derived
将不能够引用它。
完全不同的方法将注册函子。 派生类将寄存器一些功能(或成员函数)与所述基类,而在派生类的构造。 当实际功能是通过客户端调用它是一个基类函数,然后通过注册的功能迭代。 这扩展到继承的多层次,每一个派生类只需要关注其自身的功能。
看看模板方法模式 。 (基本的想法是,你不必再调用基类的方法。)
一条出路是根本不使用虚拟方法,而是允许用户注册回调,并做prepareForInsertion的工作之前调用这些。 这样,就不可能犯这种错误,因为它是基类,可以确保无论是回调和正常处理发生。 如果您希望此行为很多功能你可以有很多回调的结束。 如果你真的使用模式这么多,你可能想看看像AspectJ的工具(或任何C#相当于是),可以自动完成这样的事情。
如果你发现你可以隐藏虚拟功能,使界面非虚,尝试而不是检查,如果其他用户没有打电话给你的功能只是自己调用它。 如果你的基本代码应在结束时调用,它应该是这样的:
class Container
{
public:
void PrepareForInsertion(ObjectToInsert* pObject)
{
PrepareForInsertionImpl(pObject);
doBasePreparing(pObject);
}
protected:
virtual void PrepareForInsertionImpl(ObjectToInsert* pObject)
{
// nothing to do
}
private:
void doBasePreparing(ObjectToInsert* pObject)
{
// put here your code from Container::PrepareForInsertionImpl
}
};
文章来源: How to ensure that virtual method calls get propagated all the way to the base class?