【问题标题】:Overriding public virtual functions with private functions in C++在 C++ 中用私有函数覆盖公共虚函数
【发布时间】:2010-10-03 19:57:07
【问题描述】:

是否有任何理由使重写的 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. 是否有正当理由这样做?

【问题讨论】:

  • 重新发明“受保护”的我们吗?

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


【解决方案1】:

你确实得到了令人惊讶的结果,如果你有一个孩子,你不能调用 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 设计。

【讨论】:

  • Qt 做到了,把我带到了这里 :) 谢谢。
  • @mlvljr:但作为一个天文事件,我非常棒!
【解决方案2】:

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

在派生类中,如果您将 Base 声明为公共私有的东西设为公共私有,那么您就是在拿走一些东西。现在,即使 Derived 对象“是一个”Base 对象,您应该能够对 Base 类对象执行的操作却无法对 Derived 类对象执行,从而破坏了Liskov Substitution Prinicple

这会导致您的程序出现“技术”问题吗?也许不吧。但这可能意味着您的类的对象不会按照用户期望的方式运行。

如果您发现自己处于您想要的情况(除了另一个答案中提到的已弃用方法的情况),那么您很可能拥有一个继承模型,其中继承并不是真正建模“is-a , (例如 Scott Myers 的示例 Square 继承自 Rectangle,但您不能像矩形那样独立于其高度来更改 Square 的宽度)并且您可能需要重新考虑您的类关系。

【讨论】:

    【解决方案3】:

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

    在我看来,这会很奇怪和令人困惑。

    【讨论】:

      【解决方案4】:

      如果您使用私有继承,它会非常有用 - 即您想重用基类的(自定义)功能,而不是接口。

      【讨论】:

        【解决方案5】:

        这是可以做到的,而且非常偶尔会带来好处。例如,在我们的代码库中,我们正在使用一个库,其中包含一个具有我们过去使用的公共函数的类,但现在由于其他潜在问题不鼓励使用(有更安全的方法可以调用)。我们也碰巧有一个派生自该类的类,我们的许多代码都直接使用它。因此,我们在派生类中将给定函数设为私有,以帮助每个人记住,如果可以帮助,不要使用它。它不会消除使用它的能力,但它会在代码尝试编译时捕获一些用途,而不是在代码审查的后期。

        【讨论】:

          【解决方案6】:
          1. 没有技术问题,如果您指的是技术,因为存在隐藏的运行时成本。
          2. 如果您公开继承 base,则不应这样做。如果您通过 protected 或 private 继承,那么这有助于防止使用没有意义的方法,除非您有一个基指针。

          【讨论】:

            【解决方案7】:

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

            私有对象的示例代码:

            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 可以通过强制转换为基础对象来调用,但这更加困难,通常不应该这样做。

            【讨论】:

            • Listener/Observer接口,接收回调。
            猜你喜欢
            • 2017-06-08
            • 2021-10-22
            • 2015-06-17
            • 2018-11-16
            • 2018-07-24
            • 2011-08-10
            相关资源
            最近更新 更多