首页 文章

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

提问于
浏览
44

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

我的具体问题:

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

  • 有没有合理的理由这样做?

7 回答

  • 6

    问题是Base类方法是声明其接口的方式 . 从本质上讲,它是“你可以对这类对象做的事情 . ”

    在Derived类中,你创建了Base已声明为public private的东西,你正在拿走一些东西 . 现在,即使是Derived对象"is-a" Base对象,你应该能够对Base类对象做一些你无法对Derived类对象做的事情,打破了Liskov Substitution Prinicple

    这会在你的程序中引起“技术”问题吗?也许不吧 . 但它可能意味着您的类的对象不会以用户期望它们的行为方式运行 .

    如果你发现自己处于你想要的状态(除了在另一个答案中提到的弃用方法),你可能有一个继承模型,其中继承不是真正建模“is-a”(例如,斯科特迈尔斯的例子Square继承自Rectangle,但是你无法改变Square的宽度而不像它的高度那样,你可能需要重新考虑你的阶级关系 .

  • 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设计 .

  • 29

    没有技术问题,但最终会出现公共可用功能取决于您是否具有基指针或派生指针的情况 .

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

  • 1

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

  • 39

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

  • 4
    • 没有技术问题,如果你的意思是技术,因为存在隐藏的运行时成本 .

    • 如果你公开地继承了基础,除非你有一个基指针,否则你不应该有意义 .

  • 3

    私有继承的一个很好的用例是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 ,但是's more difficult and generally shouldn't可以完成 .

相关问题