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

Overriding public virtual functions with private functions in C++

是否有任何理由对重写的C++虚拟函数的权限与基类不同?这样做有危险吗?

例如:

1
2
3
4
5
6
7
8
9
class base {
    public:
        virtual int foo(double) = 0;
}

class child : public base {
    private:
        virtual int foo(double);
}

C++ FAQ说这是个坏主意,但并没有说明原因。

我在一些代码中看到过这个习语,我认为作者正试图使类成为最终的,基于这样一个假设,即不可能重写私有成员函数。然而,本文展示了一个覆盖私有函数的例子。当然,C++ FAQ的另一部分建议不要这么做。

我的具体问题:

  • 对于派生类和基类中的虚拟方法,使用不同的权限是否存在技术问题?

  • 有什么正当理由这样做吗?


  • 你会得到一个令人惊讶的结果,如果你有一个孩子,你不能称之为foo,但是你可以将它投射到一个基上,然后再称之为foo。

    1
    2
    3
    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设计可能会被重构。


    问题是,基类方法是它声明接口的方法。它本质上是说,"这些是你可以对这个类的对象做的事情。"

    在派生类中,当您使基声明为public private时,您将带走一些内容。现在,即使派生对象"is-a"是基对象,也应该能够对基类对象执行某些操作,而不能对派生类对象执行这些操作,从而打破了liskov替换原则

    这会导致你的程序出现"技术"问题吗?也许不是。但这可能意味着类的对象的行为不会像用户期望的那样。

    如果你发现自己处于这样一种情况下,你想要的就是这样(除了在另一个答案中提到的不推荐使用的方法),很可能你有一个继承模型,其中继承并不是真正建模的"is-a",(例如Scott Myers的示例平方继承自矩形,但是你不能改变一个独立于它的平方的宽度s的高度(就像矩形的高度一样),您可能需要重新考虑类关系。


    没有技术问题,但最终会出现一种情况,即公开可用的函数将取决于您是否有基指针或派生指针。

    在我看来,这将是奇怪和混乱的。


    如果您使用私有继承,那么它可能非常有用——即,您希望重用(自定义)基类的功能,而不是接口。


    这是可以做到的,而且偶尔也会带来好处。例如,在我们的代码库中,我们使用的库包含一个具有我们以前使用的公共函数的类,但由于其他潜在问题(有更安全的方法可调用),现在不鼓励使用。我们还碰巧有一个类派生自我们的许多代码直接使用的那个类。因此,我们在派生类中将给定的函数设为私有,以便帮助每个人记住,如果他们能够帮助的话,就不要使用它。它并没有消除使用它的能力,但是当代码试图编译时,它会捕捉到一些使用,而不是稍后在代码检查中。


    私有继承的一个好的用例是侦听器/观察者事件接口。

    私有对象的示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class AnimatableListener {
      public:
        virtual void Animate(float delta_time);
    };

    class BouncyBall : public AnimatableListener {
      public:
        void TossUp() {}
      private:
        void Animate(float delta_time) override { }
    };

    对象的某些用户需要父功能,而有些用户需要子功能:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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,但这更困难,一般不应该这样做。


  • 没有技术问题,如果你的意思是技术上有一个隐藏的运行时成本。
  • 如果你公开继承基地,你不应该这样做。如果通过protected或private继承,那么这有助于防止使用没有意义的方法,除非您有基指针。