使用C ++中的私有函数覆盖公共虚函数

时间:2009-01-27 18:24:15

标签: c++ override access-control virtual-functions

是否有任何理由使重写的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建议不要这样做。

我的具体问题:

  1. 在派生类和基类中使用不同的虚拟方法权限是否有任何技术问题?

  2. 有没有合理的理由这样做?

7 个答案:

答案 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)

  1. 没有技术问题,如果你的意思是技术,因为存在隐藏的运行时成本。
  2. 如果您公开继承基础,则不应该这样做。如果你通过protected或private继承,那么这可以帮助防止使用没有意义的方法,除非你有一个基指针。

答案 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,但这样做更困难,通常不应该这样做。