English 中文(简体)
在C++中使用私有函数来覆盖公有虚函数
原标题:
  • 时间:2009-01-27 18:24:15
  •  标签:

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

例如:

class base {
    public:
        virtual int foo(double) = 0;
}

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

《C++ 常见问题解答》(C++ faq)指出这是一种不好的做法,但没有解释原因。

我曾在一些代码中看到过这个成语,我相信作者试图使类成为final,基于这样的假设:无法覆盖私有成员函数。然而,该文章展示了覆盖私有函数的示例。当然C++ FAQ的另一部分建议不这样做。

我的具体问题:

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

  2. 是否有合法的理由这么做?

最佳回答

问题在于基类方法是声明其接口的方式。实际上,它在说:“这些是您可以对此类对象执行的操作。”

当在派生类中将基类声明为公共的东西变为私有的时候,你正在拿走一些东西。现在,尽管派生对象“是一个”基对象,但你应该能够对基类对象做的某些事情,你不能对派生类对象做,从而破坏了Liskov替代原则。

这会在你的程序中引起“技术”问题吗?也许不会。但这可能意味着你的类的对象不会按照你的用户期望的方式行事。

如果你发现自己处于这种希望的情况(除非是另一个答案所提到的弃用方法),那么你很可能有一个继承模型,其中继承并没有真正地建模“是一个”(例如Scott Myers的例子:正方形继承自矩形,但是你不能单独改变正方形的宽度而不影响它的高度,就像你可以改变矩形一样),这时你可能需要重新考虑你的类关系。

问题回答

你会惊讶地发现,如果你有一个孩子,你无法调用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. No technical problem, if you mean by technical as there being a hidden runtime cost.
  2. If you inherit base publically, you shouldn t do this. If you inherit via protected or private, then this can help prevent using methods that don t make sense unless you have a base pointer.

私有继承的一个很好的用例是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可以通过强制转换为基本对象来调用,但这更加困难并且通常不应该这样做。





相关问题
热门标签