Car*_*org 14 c++ virtual inheritance
类层次结构的一个常见错误是将基类中的方法指定为虚拟,以便继承链中的所有重写都能完成某些工作,并且忘记将调用传播到基本实现.
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!
}
};
Run Code Online (Sandbox Code Playgroud)
我的问题是:是否有一种好的方法/模式来确保在调用链的末尾始终调用基本实现?
我知道有两种方法可以做到这一点.
您可以使用成员变量作为标志,将其设置为虚方法的基本实现中的正确值,并在调用后检查其值.这需要使用公共非虚方法作为客户端的接口,并使虚方法受到保护(这实际上是一件好事),但它需要专门为此目的使用成员变量(需要如果虚方法必须是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);
}
};
Run Code Online (Sandbox Code Playgroud)
另一种方法是用不透明的"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);
}
};
Run Code Online (Sandbox Code Playgroud)
在我看来,这种方法不如第一种方法,但它确实避免使用专用的成员变量.
还有哪些其他可能性?
tpd*_*pdi 22
您已经想出了一些聪明的方法来实现这一点,(在您承认的情况下)膨胀类的成本并添加代码来解决对象的责任而不是程序员的缺陷.
真正的答案是不要在运行时这样做.这是程序员错误,而不是运行时错误.
在编译时执行:如果语言支持语言构造,则使用语言构造,或使用模式执行它(例如,模板方法),或使编译依赖于测试传递,并设置测试以强制执行它.
或者,如果传播失败导致派生类失败,则让它失败,并使用异常消息通知派生类的作者他未能正确使用基类.
Dan*_*ing 13
您正在寻找的只是非虚拟接口模式.
它与您在此处所做的类似,但保证基类实现被调用,因为它是唯一可以调用的实现.它消除了上述示例所需的混乱.通过基类调用是自动的,因此派生版本不需要进行显式调用.
谷歌"非虚拟接口"的详细信息.
编辑:查找"模板方法模式"后,我看到它是非虚拟接口的另一个名称.我之前从未听过这个名字(我不是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.
}
};
Run Code Online (Sandbox Code Playgroud)
当只有一个级别的继承时,您可以使用模板方法模式,其中公共接口是非虚拟的并调用虚拟实现函数.然后基数的逻辑进入公共函数,确保被调用.
如果你有多个级别的继承,并希望每个类调用它的基类,那么你仍然可以使用模板方法模式,但是如果扭曲,使虚拟函数的返回值只能被构造, 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();
}
};
Run Code Online (Sandbox Code Playgroud)
如果你需要public(非virtual)函数返回一个值,那么内部函数virtual应该返回std::pair<return-type , remember_to_call_base>.
注意事项:
remember_to_call_base有一个显式的构造函数声明为private,所以只有它friend(在这种情况下base)可以创建这个类的新实例.remember_to_call_base 没有明确定义的复制构造函数,因此编译器将创建一个具有public可访问性的构造函数,允许从base实现中按值返回它.remember_to_call_base在该protected部分中声明base,如果它在该private部分derived中将根本无法引用它.| 归档时间: |
|
| 查看次数: |
2333 次 |
| 最近记录: |