【问题标题】:Overriding rules in C++C++ 中的覆盖规则
【发布时间】:2012-01-03 10:23:40
【问题描述】:

从 Java 的角度来看,我惊讶地发现您只能覆盖具有 virtual 关键字的基本方法。 在 Java 中,您使用 final 关键字来声明一个方法不能被覆盖。

我的想法是,您很少希望禁止覆盖,以便有人可以按照他们认为合适的方式扩展您的类。

所以在 C++ 中,如果您觉得有人可能希望在某个阶段从您的类继承(也许几年后有人认为这是一个很酷的想法),您是否将所有方法都设为虚拟?

或者是否有一些我不知道的想要在 C++ 中禁止这一点的关键原因?

作为参考,这是我在每种语言中所做的实验:

Java

    public class Base {

    void doSomething(){
    System.out.println("Doing the base thing");
    }
    }
    public class Derived extends Base {

    void doSomething(){
    System.out.println("Doing the derived thing");
    }

    public static void main(String... argv){
        Base object = new Derived();
        object.doSomething();
    }
    }

C++

    class Base
    {
    public:
        Base(void);
        ~Base(void);
        virtual void doSomething();
    };

    void Base::doSomething(){
        std::cout << "I'm doing the base thing\n" << std::endl;
    }

    class Derived :
        public Base
    {
    public:
        Derived(void);
        ~Derived(void);
        void doSomething();
    };

    void Derived::doSomething(){
        std::cout << "I'm doing the dervied thing" << std::endl;
    }


    int main(void){

        Base * object = new Derived;

        object->doSomething();
        return 0;

    }

【问题讨论】:

标签: java c++ overriding


【解决方案1】:

duffymo 和 Als 正在引导您朝着正确的方向前进。我只想评论你说的一件事:

所以在 C++ 中,如果你觉得有人可能想在某个阶段继承 从你的班级(也许几年后有人认为这是一个很酷的想法)做 你把你所有的方法都虚拟化了吗?

从软件工程的角度来看:如果您没有立即使用继承并且没有使用接口,那么我不建议您将方法声明为虚拟。

虚拟方法的性能下降非常轻微。对于非关键代码路径,性能影响可能可以忽略不计。但是对于经常被调用的类方法,它可以加起来。编译器不能做尽可能多的内联和直接链接。相反,要调用的虚方法必须在运行时在 v-table 数组中查找。

当我的编码团队中的某个人开始与“有人可能会在稍后的某个时间……”进行设计对话时,我的“面向未来”的反模式警报就会响起。设计可扩展性是一回事,但“面向未来的功能”应该推迟到那时。

此外 - 多年后认为这是一个很酷的想法的人 - 让他成为拥有将类方法转换为虚拟的人。无论如何,到那时你将从事更大的项目。 :)

【讨论】:

  • 请注意,虚拟方法的性能影响仅适用于 c++ 或实际上有不同的覆盖方法(即使这样也可以乐观地内联等)
【解决方案2】:

是的,在 C++ 中,如果在基类中标记为 virtual,则类方法只能是 overidden

如果您的类是为继承而设计的,并且您的类方法旨在为基类和派生类提供不同的行为,则将该方法标记为virtual

好读:

When to mark a function in C++ as a virtual?

【讨论】:

    【解决方案3】:

    是的,您必须将所有方法设为虚拟。

    Java 的立场是,默认情况下,一切都是公平的游戏,可以覆盖,并且禁止它需要采取行动。 C++ 和 C# 采取相反的观点。

    【讨论】:

      【解决方案4】:

      即使没有virtual,您也可以覆盖基类中的方法。

      以这个小程序为例:

      #include <iostream>
      
      struct A
      {
          void m()
              { std::cout << "A\n"; }
      
          virtual void n()
              { std::cout << "A\n"; }
      };
      
      struct B : public A
      {
          void m()
              { std::cout << "B\n"; }
      
          virtual void n()
              { std::cout << "B\n"; }
      };
      
      int main()
      {
          A a;
          a.m();
          a.n();
      
          B b;
          b.m();
          b.n();
      
          A &c = b;
          c.m();
          c.n();
      }
      

      程序的输出是:

      一种 一种 乙 乙 一种 乙

      如您所见,B::m 方法覆盖了A 中的相同方法。但这仅在使用 B 的确切实例时才有效。在第三种情况下,当使用对基类的引用时,您需要virtual 才能使其工作。

      【讨论】:

      • virtual 关键字对于overidding 是必需的,或者您得到的是Function hiding。派生结构B 中的方法m() 隐藏了基础结构A 方法,而B 中的方法n() 覆盖了基础结构方法。
      • 您在 B 中对 m() 所做的操作称为重载(这也会导致基类版本被隐藏),而不是覆盖。在 C++ 中,必须将函数显式声明为 virtual 以使其可覆盖。
      【解决方案5】:

      来自默认使用虚函数的语言的程序员往往会惊讶于 C++ 有相反的默认选择,即非虚是默认设置。但是请注意,[具体] 虚函数的合约更难记录和测试,因为实际上涉及两个不同的合约:

      1. 所有重写函数的契约,即函数在概念上的作用
      2. 具体功能的约定

      仅记录其中一个并不能真正削减它,因为另一半根本不清楚。这也意味着您无法从特定的实现中一目了然,实际合同是什么(假设您处于文档由源提供的不太典型的情况)。

      【讨论】:

      • 举个例子,为什么让所有东西都虚拟化或继承一般可能不是一个好主意,真的经常看看那些从 Java 中的HashMap 继承的人,结果并不是他们以为会的。
      猜你喜欢
      • 2014-01-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多