是否有任何理由使重写的C ++虚函数的权限与基类不同?这样做有危险吗?
例如:
class base {
public:
virtual int foo(double) = 0;
}
class child : public base {
private:
virtual int foo(double);
}
C++ faq说这是一个坏主意,但没有说明原因。
我在一些代码中看到了这个习惯用法,我相信作者试图让这个类最终,基于一个假设,即不可能覆盖私有成员函数。但是,This article显示了覆盖私有函数的示例。当然another part of the C++ faq建议不要这样做。
我的具体问题:
在派生类和基类中使用不同的虚拟方法权限是否有任何技术问题?
有没有合理的理由这样做?
答案 0 :(得分:39)
你确实得到了令人惊讶的结果,如果你有一个孩子,你不能打电话给foo,但是你可以把它扔到基地,然后打电话给foo。
child *c = new child();
c->foo; // compile error (can't access private member)
static_cast<base *>(c)->foo(); // this is fine, but still calls the implementation in child
我想你可以设想一个你不想暴露函数的例子,除非你把它当作基类的一个实例。但是,这种情况突然出现的事实表明,在可能被重构的线路上某处有一个糟糕的OO设计。
答案 1 :(得分:30)
问题是Base类方法是声明其接口的方式。从本质上讲,它是“这些是你可以对这个类的对象做的事情。”
在Derived类中,你创建了Base已声明为public private的东西,你正在拿走一些东西。现在,即使Derived对象是“is-a”Base对象,你应该对Base类对象做一些你无法对Derived类对象做的事情,打破Liskov Substitution Prinicple
这会在你的程序中引起“技术”问题吗?也许不吧。但它可能意味着您的类的对象不会以用户期望它们的行为方式运行。
如果你发现自己处于你想要的状态(除了在另一个答案中提到的弃用方法),你可能有一个继承模型,其中继承不是真正的建模“is-a ,“(例如,斯科特迈尔斯的例子Square继承自Rectangle,但你不能像平方一样改变Square的宽度而不像它的高度),你可能需要重新考虑你的阶级关系。
答案 2 :(得分:6)
没有任何技术问题,但最终会出现这样一种情况,即公开可用的功能将取决于您是否具有基本指针或派生指针。
这在我看来会很奇怪而且令人困惑。
答案 3 :(得分:4)
如果您使用私有继承,那么它非常有用 - 即您希望重用基类的(自定义)功能,而不是接口。
答案 4 :(得分:3)
可以做到,偶尔会带来好处。例如,在我们的代码库中,我们使用的库包含一个我们以前使用的具有公共函数的类,但现在不鼓励使用其他潜在的问题(有更安全的方法可以调用)。我们碰巧有一个派生自该类的类,我们的许多代码直接使用它。因此,我们在派生类中将给定函数设为私有,以帮助每个人记住如果他们可以帮助它就不要使用它。它并没有消除使用它的能力,但它会在代码尝试编译时捕获一些用途,而不是在代码审查的后期。
答案 5 :(得分:1)
答案 6 :(得分:1)
私有继承的一个很好的用例是Listener / Observer事件接口。
私有对象的示例代码:
class AnimatableListener {
public:
virtual void Animate(float delta_time);
};
class BouncyBall : public AnimatableListener {
public:
void TossUp() {}
private:
void Animate(float delta_time) override { }
};
该对象的某些用户想要父功能,而有些用户想要子功能:
class AnimationList {
public:
void AnimateAll() {
for (auto & animatable : animatables) {
// Uses the parent functionality.
animatable->Animate();
}
}
private:
vector<AnimatableListener*> animatables;
};
class Seal {
public:
void Dance() {
// Uses unique functionality.
ball->TossUp();
}
private:
BouncyBall* ball;
};
这样AnimationList
可以保存对父项的引用并使用父功能。虽然Seal
包含对子项的引用,但使用唯一的子功能并忽略父项。在此示例中,Seal
不应调用Animate
。现在,如上所述,可以通过强制转换为基础对象来调用Animate
,但这样做更困难,通常不应该这样做。